{"owner":"Significant-Gravitas","repo":"AutoGPT","hasSkills":true,"totalSkillsCount":109,"totalTokensCount":78334,"categories":["anthropic-skill","root-instruction","claude-rule","copilot-instructions","subagent-persona"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/perf-compare/SKILL.md",".claude/skills/pr-review/SKILL.md",".claude/skills/open-pr/SKILL.md",".claude/skills/pr-polish/SKILL.md",".claude/skills/pr-address/SKILL.md",".claude/skills/orchestrate/SKILL.md",".claude/skills/worktree/SKILL.md","autogpt_platform/backend/test/fixtures/skills/webapp-testing/SKILL.md",".claude/skills/setup-repo/SKILL.md",".claude/skills/vercel-react-best-practices/SKILL.md",".claude/skills/write-frontend-tests/SKILL.md",".claude/skills/pr-test/SKILL.md","AGENTS.md","CLAUDE.md",".github/copilot-instructions.md","autogpt_platform/AGENTS.md","autogpt_platform/CLAUDE.md","classic/CLAUDE.md","docs/AGENTS.md","docs/CLAUDE.md",".claude/skills/vercel-react-best-practices/AGENTS.md","autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json","autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json","autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json","autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json","autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json","autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json","autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json","autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json","autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json","autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json","autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json","autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json","autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json","autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json","autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json","autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json","autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json","autogpt_platform/backend/agents/calculator-agent.json",".claude/skills/orchestrate/scripts/capacity.sh",".claude/skills/orchestrate/scripts/classify-pane.sh",".claude/skills/orchestrate/scripts/find-spare.sh",".claude/skills/orchestrate/scripts/notify.sh",".claude/skills/orchestrate/scripts/poll-cycle.sh",".claude/skills/orchestrate/scripts/recycle-agent.sh",".claude/skills/orchestrate/scripts/run-loop.sh",".claude/skills/orchestrate/scripts/spawn-agent.sh",".claude/skills/orchestrate/scripts/status.sh",".claude/skills/orchestrate/scripts/verify-complete.sh",".claude/skills/perf-compare/scripts/perf_compare.py",".claude/skills/perf-compare/scripts/test_perf_compare.py",".claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md",".claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md",".claude/skills/vercel-react-best-practices/rules/async-api-routes.md",".claude/skills/vercel-react-best-practices/rules/async-defer-await.md",".claude/skills/vercel-react-best-practices/rules/async-dependencies.md",".claude/skills/vercel-react-best-practices/rules/async-parallel.md",".claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md",".claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md",".claude/skills/vercel-react-best-practices/rules/bundle-conditional.md",".claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md",".claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md",".claude/skills/vercel-react-best-practices/rules/bundle-preload.md",".claude/skills/vercel-react-best-practices/rules/client-event-listeners.md",".claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md",".claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md",".claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md",".claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md",".claude/skills/vercel-react-best-practices/rules/js-cache-storage.md",".claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md",".claude/skills/vercel-react-best-practices/rules/js-early-exit.md",".claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md",".claude/skills/vercel-react-best-practices/rules/js-index-maps.md",".claude/skills/vercel-react-best-practices/rules/js-length-check-first.md",".claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md",".claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md",".claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md",".claude/skills/vercel-react-best-practices/rules/rendering-activity.md",".claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md",".claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md",".claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md",".claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md",".claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md",".claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md",".claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md",".claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md",".claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md",".claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md",".claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md",".claude/skills/vercel-react-best-practices/rules/rerender-memo.md",".claude/skills/vercel-react-best-practices/rules/rerender-transitions.md",".claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md",".claude/skills/vercel-react-best-practices/rules/server-cache-lru.md",".claude/skills/vercel-react-best-practices/rules/server-cache-react.md",".claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md",".claude/skills/vercel-react-best-practices/rules/server-serialization.md","classic/original_autogpt/autogpt/agents/README.md",".claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json",".claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json",".claude/skills/perf-compare/scripts/fixtures/error_400.json",".claude/skills/perf-compare/scripts/fixtures/project_builder.json",".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json",".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json",".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json",".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json","autogpt_platform/backend/backend/copilot/tools/IDEAS.md","autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json","autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json","autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md"],"skills":{".claude/skills/perf-compare/SKILL.md":"---\nname: perf-compare\ndescription: \"Shared measurement recipe for AutoGPT performance work. Compares a baseline and a candidate in Sentry (two releases, two environments such as preview pr-<n> deploys, or two time windows) per transaction: count, p50/p95/p99, failure rate, and a trace-clustered bootstrap verdict of faster / slower / no detectable change / not enough data, with exit codes agents can branch on. Works for span duration, web vitals (measurements.lcp) or any numeric span field. TRIGGER when an agent needs to show a change made the backend or frontend faster or slower, compare a preview against a control or dev, check a release after merge on dev or prod, or check for regressions.\"\nuser-invocable: true\nargument-hint: \"[what to compare, e.g. 'builder release OLD vs NEW on prod' or 'pr-123 vs control pr-124 on /api/library/agents']\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# perf-compare: one way to measure performance\n\nEvery agent that tunes speed or stability measures with this recipe, so results are comparable across agents and noise is not reported as a win. Sentry is the store: org `significant-gravitas`, API `https://us.sentry.io`, tracing at 100%. `scripts/perf_compare.py` (Python 3.8+, standard library only) runs the queries and the statistics.\n\n```bash\nS=$(git rev-parse --show-toplevel)/.claude/skills/perf-compare/scripts/perf_compare.py\npython3 \"$S\" --help\n```\n\n## When to use it\n\n- Before claiming a change made something faster or slower, on a preview or after merge.\n- After merge, to check a win holds on dev and then prod (release against release).\n- To check a change did not regress other transactions (run without `--transaction`).\n- For any numeric field: `span.duration` (default), web vitals on builder pageloads (`measurements.lcp`), LLM token counts (`gen_ai.usage.input_tokens`), or custom attributes (`tags[name,number]`).\n\nIt is not for debugging one slow request (open the trace) or for error triage.\n\n## Token\n\nThe script reads `SENTRY_AUTH_TOKEN` from the environment and nowhere else. It needs a read-only token with exactly `org:read`, `project:read` and `event:read`: either a user auth token (Settings > Account > API > Auth Tokens) or an internal integration with Read on Organization, Project and Issue & Event. Organization auth tokens (`sntrys_...`) carry only `org:ci` and get HTTP 403 on these endpoints. A missing token exits with code 5 and these instructions. `--print-queries` needs no token.\n\n## Where things are in Sentry\n\n| Project | What it holds | Environments | `release` |\n|---|---|---|---|\n| `autogpt-server` | Backend: FastAPI routes (`/api/library/agents`, `/api/chat/sessions/{session_id}/stream`) and internal RPC services (`/update_node_execution_status`) | `app:prod-behave:cloud`, `app:dev-behave:cloud` | git sha once the backend sets it; check first |\n| `builder` | Frontend (Next.js on Vercel): `http.server` routes (`GET /api/proxy/[...path]`), `middleware GET`, `serverAction/*`, browser `pageload` and `navigation` with web vitals | `app:prod-behave:cloud` is prod; `app:preview-behave:cloud` is dev **and** every Vercel preview | git sha |\n| `autogpt-server-previews`, `builder-previews` | `!deploy` previews | `pr-<n>` | git sha |\n\nCheck a selector has data before you measure:\n\n```text\nsearch_events  projectSlug=<project>  dataset=spans  query=\"is_transaction:true\"\n               fields=[\"environment\",\"release\",\"count()\"]  sort=\"-count()\"  period=\"7d\"\n```\n\nIf `release` comes back null for a project, select by time window instead of release. If `autogpt-server-previews` has no `pr-<n>` events, that preview's backend is reporting into `autogpt-server` as `app:dev-behave:cloud`, mixed with dev traffic, and cannot be measured separately.\n\n## Choosing baseline and candidate\n\nBoth sides take `KEY=VALUE` pairs: `project=`, `env=`, `release=`, `query=` (extra Sentry filter), and time as `window=START/END` (repeatable), `start=`/`end=`, or `period=24h`. Shared defaults come from `--project`, `--env`, `--query` and `--period`/`--start`/`--end`/`--window`. A side's own `project=`, `env=` or `query=` wins over the shared value (`env=` with nothing after it clears it). Any time key on a side (`window=`, `start=`/`end=` or `period=`) replaces all of the shared time flags for that side. Times are UTC ISO 8601; without a time anywhere, both sides use the last 7 days.\n\n**1. After merge: release against release (dev or prod).** Same project and environment, previous release against new release, in windows of equal length that cover the same hours of the day: the 24 hours before the deploy against the 24 hours after, or 7 days against 7 days for quiet routes. Always filter by release when there is one. Old releases keep sending events for days (open tabs, Vercel skew protection), so a time window alone mixes releases.\n\n```bash\npython3 \"$S\" --project builder --env app:prod-behave:cloud \\\n  --baseline release=OLD_SHA window=2026-10-10T14:00:00Z/2026-10-11T14:00:00Z \\\n  --candidate release=NEW_SHA window=2026-10-11T14:00:00Z/2026-10-12T14:00:00Z\n```\n\n**2. On a preview: candidate preview against a control preview.** Deploy the change with `!deploy`, and deploy a control (the dev commit the change branches from, with no changes) the same way. Each gets its own namespace and an empty branch database, so the two are alike. Drive the same load at both, either together in one window or interleaved A B B A (control, candidate, candidate, control; about 10 minutes each) so drift cancels:\n\n```bash\npython3 \"$S\" --project autogpt-server-previews --transaction '/api/store/agents' \\\n  --baseline env=pr-CONTROL window=T0/T1 window=T3/T4 \\\n  --candidate env=pr-CANDIDATE window=T1/T2 window=T2/T3\n```\n\nMeasuring the candidate preview against `app:dev-behave:cloud` at dev HEAD in the same window is cheaper but weaker: dev has a full database, real traffic and different pods. Use it as a rough read, not as proof.\n\n**3. No release tag: before against after.** Equal windows either side of the deploy. Other deploys inside those windows also change things, so say so in your report.\n\n**4. A/A check before trusting a win.** Compare a selector with itself over two windows, for example the same release yesterday and today. It should say `no detectable change`. If it does not, your windows differ too much (hour of day, weekday, traffic mix), and so will your real comparison.\n\n## How much data\n\n- The script gives no p50 or p95 verdict unless each side has `--min-count` samples (default 200) **from** `--min-traces` distinct traces (default 30). At 200, p95 rests on 10 values above it. p99 is shown but never judged. The verdict cell says which minimum was missed. The failure-rate test works from Sentry's event counts instead, and needs `--min-count` events per side.\n- It samples up to `--sample-cap` values per side per transaction (default 1000, at most 20000), paging 100 at a time. A cap below `--min-count` is refused.\n- For a claim about one transaction, name it with `--transaction` and raise `--sample-cap` to 2000–5000. Naming it also shrinks the multiple-comparison correction (see below).\n- Correlated spans count once: the bootstrap resamples whole traces, and the trace minimum stops a few busy traces (a page polling an endpoint) from passing as a big sample. The table shows samples and distinct traces. The effective sample is closer to the trace count than the span count. In A/A tests on clustered data, resampling spans one at a time called 38–44% of identical pairs a change, against 2–5% when resampling by trace.\n- Drive at least 1000 requests per side per transaction you care about. Wait a few minutes after load stops before measuring, so Sentry has the last events.\n\n## Driving load on a preview\n\nA preview's backend is `https://pr-<N>-server.agpt.co` (REST under `/api`, health at `/health`), and its frontend is `https://autogpt-pr-<N>.vercel.app`.\n\nThe k6 tests are in `autogpt_platform/backend/load-tests/`; its `README.md` covers every test and setting. Run them on your own machine: `./k6.sh` runs k6 in Docker (`grafana/k6`), so nothing else needs installing, and a local run costs nothing beyond the load on the target. Do not run them on Grafana Cloud k6. It bills every virtual-user hour, so `./k6.sh` refuses cloud runs unless they are explicitly opted into, and the README reserves them for rare runs the team has agreed to. `K6_ENVIRONMENT=PREVIEW` with `PR_NUMBER=<N>` targets a preview:\n\n```bash\ncd autogpt_platform/backend/load-tests\nexport K6_ENVIRONMENT=PREVIEW PR_NUMBER=<N>\nVUS=5 DURATION=10m RAMP_UP=30s RAMP_DOWN=30s ./k6.sh run tests/api/core-api-test.js\n```\n\n- **Authentication needs no setup on a preview.** The authenticated tests (`tests/api/*`, `tests/basic/*`, `tests/marketplace/library-access-test.js`, `tests/comprehensive/platform-journey-test.js`) sign up throwaway `loadtest-*@previews.agpt.co` accounts in `setup()`, once per run, and use the Better Auth tokens the preview's frontend issues for them. `LOAD_TEST_ACCOUNT_COUNT` sets how many (default 5). `tests/marketplace/public-access-test.js` needs no account at all.\n- **To run as specific accounts**, such as the seeded `preview-*@previews.agpt.co` personas, set `LOAD_TEST_ACCOUNTS` to a JSON array of `{\"email\", \"password\"}`. The tests sign each one in once per run. Dev has no throwaway sign-up, so it always needs `LOAD_TEST_ACCOUNTS`.\n- **Note the run's UTC window** (`date -u +%FT%TZ` before and after) for the baseline and candidate windows above, and drive the same test, VUs and duration at both sides.\n- **Previews start with an empty database.** Store and library lists return nothing, so costs that grow with data (N+1 queries, scans) do not show. Seed data through the API first, or confirm on dev and prod after merge.\n- None of the k6 tests open copilot chats. To measure copilot turns, run chats against the preview yourself, or measure on dev and prod traffic.\n- The repository's evals (`backend/copilot/eval`, `backend/copilot/capabilities/eval`, `poetry run expert-style-eval`, `poetry run brain-dump-eval`) are offline quality and cost harnesses. They send no traffic to a deploy.\n- After merge, real traffic on dev and prod is the load.\n\n## Reading the verdict\n\nThe markdown output (default) or `--json` reports two things for each transaction.\n\n- **Sentry aggregates over every matching event:** count, p50, p95, p99 and failure rate. These are exact for the windows.\n- **A verdict for p50 and p95:**\n  - The interval is a bootstrap CI of candidate minus baseline from the raw samples, resampling whole traces.\n  - It is Bonferroni-adjusted so that every interval in the run holds together at `--confidence` (95%). Two transactions means four intervals at 98.75% each. The plain 95% interval is in the JSON as `ci`.\n  - Resamples are raised automatically until each tail of the adjusted interval holds 20 draws, up to 50000. For example, 4000 becomes 8000 for five transactions. At the 50000 cap a run still goes ahead with as few as 10 draws per tail. A run that would leave fewer than 10, because too many transactions are compared, is refused before it starts (exit 2). The JSON reports the number used as `settings.resamples`.\n  - `faster` or `slower` needs the adjusted interval to exclude zero, and **both** the sample change and Sentry's all-events change to be at least `--min-effect` (5%) in that direction. Anything else is `no detectable change`. Below `--min-count` samples or `--min-traces` traces it is `not enough data`.\n  - The transaction takes its worst percentile: `slower` if either is slower, else `faster` if either is faster.\n- **Failure rate:** Fisher's exact test on the failure counts behind Sentry's `failure_rate()`. It is exact because a normal approximation is wrong with a handful of failures: 0 of 1000 against 4 of 1000 passes a z-test, but Fisher gives p = 0.12.\n  - The significance level is Bonferroni-adjusted over the K transactions that have `--min-count` events on both sides. That is one test per transaction, not two.\n  - `higher` needs significance and a rise of at least `--min-failure-delta` (0.1 percentage points). It counts as a regression even when the transaction got faster, because requests that fail fast look like a speedup.\n  - The test treats every event as independent, and `--min-traces` does not apply to it. When failures come from a few traces (one broken page retrying), it overstates significance. Check `sample_traces` and the failing traces in Sentry before acting on a failure-rate result alone.\n\n`no detectable change` means \"not distinguishable from noise with this much data\". It does not mean \"no change\". Check the interval widths and trace counts before concluding anything.\n\n**A win counts** only when all of these hold:\n\n- you named the transaction with `--transaction` before measuring;\n- the verdict is `faster`;\n- the failure rate is not `higher`;\n- it holds again on a second, independent window or run.\n\nA run over the top transactions is for spotting regressions and leads, not for claiming wins.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | faster: a compared transaction got faster and nothing regressed |\n| 1 | slower or more failures: at least one compared transaction regressed |\n| 2 | usage error: bad arguments (including a `--region-url` that is not https, `--timeout` ≤ 0, or `--sample-cap` below `--min-count`), unknown project, or Sentry rejected the query (HTTP 400) |\n| 3 | no detectable change |\n| 4 | not enough data: no compared transaction met `--min-count` and `--min-traces` on both sides |\n| 5 | auth: `SENTRY_AUTH_TOKEN` missing, or Sentry rejected it (401/403) |\n| 6 | Sentry API or network error after retries, or any unexpected error; never read as a regression |\n\nWith `--json`, the fields to branch on are:\n\n- `overall.verdict`, `overall.exit_code` and `overall.reasons`;\n- per transaction in `transactions[]`:\n  - `verdict`, `note` (why there is not enough data), `regression`;\n  - `p50`/`p95`: `baseline`, `candidate`, `diff`, `rel`, `sentry_rel`, `ci`, `ci_adjusted`, `verdict`;\n  - `failure_rate`: `baseline`, `candidate`, `diff`, `p_value`, `verdict`;\n  - `baseline`/`candidate`: `count`, `p50`, `p95`, `p99`, `failure_rate`, `sample_n`, `sample_traces`;\n- `settings`, holding the values actually used: `sample_cap`, `resamples`, `comparisons`, `failure_comparisons`, `adjusted_confidence`, `min_count`, `min_traces`, `min_effect`, `seed`.\n\nErrors in `--json` mode print `{\"error\": {\"type\", \"message\"}, \"exit_code\"}`, where `type` is `usage`, `auth`, `api` or `internal`.\n\n## The exact queries\n\nAll requests are `GET https://us.sentry.io/api/0/organizations/significant-gravitas/events/` with `dataset=spans` and `project=<numeric id>`. The id comes from `GET /api/0/projects/significant-gravitas/<slug>/`. `start` and `end` (UTC) span the side's windows; the query's timestamp filter pins each window exactly. `F` is `--field`.\n\n1. **Aggregate per transaction**, one request per side:\n   - `field=transaction`, `count()`, `p50(F)`, `p95(F)`, `p99(F)`, `failure_rate()`\n   - `sort=-count()`, `per_page=100`\n2. **Raw sample**, per side per compared transaction:\n   - `field=F`, `trace`, `id`\n   - `sort=id`, `per_page=100`, paged with the `cursor` from the `Link` header (`rel=\"next\"; results=\"true\"`) until `--sample-cap` rows.\n   - Span ids are random, so the first N rows by `id` are a uniform random sample of the windows. Sorting by `-timestamp` instead returns the latest burst, often a few traces.\n\nBoth use this query string:\n\n```text\nis_transaction:true environment:\"ENV\" release:\"SHA\" [has:F] [(EXTRA QUERY)]\n  [transaction:\"T\" | (transaction:\"A\" OR transaction:\"B\")]\n  timestamp:>=2026-10-11T14:00:00Z timestamp:<2026-10-12T14:00:00Z\n```\n\nSeveral windows become `(timestamp:>=S1 timestamp:<E1 OR timestamp:>=S2 timestamp:<E2)`. `has:F` appears for any field other than `span.duration`.\n\n`python3 \"$S\" ... --print-queries` prints the exact REST URL and MCP arguments for an invocation, without a token.\n\n**With only the Sentry MCP**, use `search_events` with `dataset: \"spans\"`, or `search_traces` without `dataset`:\n\n- Pass `projectSlug`, the same `query`, `fields` and `sort`.\n- Pass a `period` long enough to cover the windows, such as `\"50h\"`; the timestamp filter does the exact cut. The argument is `period`, not `statsPeriod`, and an unknown key silently falls back to 14 days.\n- Pass `limit` ≤ 100.\n- Confirm the \"Executed Search\" block in the reply shows your query unchanged.\n- The MCP cannot page. For more than 100 samples, split each window into sub-windows, get `count()` for each, and take the first rows by `id` from each in proportion to its count.\n\n## Field names checked against live data\n\nChecked through the Sentry MCP on `builder` and `autogpt-server`.\n\n- **Filters:**\n  - `is_transaction:true` and `transaction:\"…\"`.\n  - `environment:\"…\"`: quote it, the values contain colons.\n  - `release:\"<sha>\"`.\n  - `span.op:` with `pageload`, `navigation`, `http.server`, `http.server.middleware` or `function.server_action`.\n  - `span.status:[…]` and `!span.status:[…]`.\n  - `has:measurements.lcp` and `has:tags[gen_ai.usage.input_tokens,number]`.\n  - `timestamp:>=…Z timestamp:<…Z` and parenthesised `OR` groups.\n- **Aggregates:**\n  - `count()`.\n  - `p50()`, `p75()`, `p95()` and `p99()`, of `span.duration` (ms), `measurements.lcp`, `measurements.fcp`, `measurements.ttfb`, `measurements.cls`, `gen_ai.usage.input_tokens` or `tags[gen_ai.usage.input_tokens,number]`.\n  - `failure_rate()`: the share of spans whose `span.status` is not `ok`, `cancelled` or `unknown`. It reconciles exactly with `count()` and `!span.status:[ok,cancelled,unknown]`.\n- **Sample fields:** `span.duration`, `trace`, `id`, `timestamp`, `release`. Sorting works by `id`, `-id` and `-timestamp`.\n- **`span.status` values seen:** `ok`, `internal_error`, `resource_exhausted`, `cancelled`, `deadline_exceeded`, `invalid_argument`, `not_found`, `already_exists`.\n- **Rejected:** wildcards on `id` such as `id:0*`; span ids must be full 16-character hex.\n\n## Pitfalls\n\n- **Environment names collide.** Builder's dev deploy and every Vercel preview share `app:preview-behave:cloud`. Tell them apart with `release`.\n- **Proxy routes measure the backend.** `GET /api/proxy/[...path]` mostly measures the backend behind it, not the frontend release, and a few polling pages contribute most of its spans.\n- **Middleware durations are quantized** to about 1 ms. A one-step move in p95 reads as +100%; look at the absolute change.\n- **Long-lived transactions measure session length.** `/ws` and `/api/chat/sessions/{session_id}/stream` last minutes to an hour, not request latency. Compare per-turn or per-node transactions instead.\n- **Weekday and hour of day move latency.** Saturday-to-Sunday against Sunday-to-Monday is not a fair pair for a release comparison. Use full days, the same hours, and the A/A check.\n- **Do not rerun until it says faster.** Repeating runs, or trying many transactions and reporting the best, turns noise into wins. Decide the transaction, windows and settings first, then run once and replicate once.\n\n## Tests\n\n```bash\npython3 -m unittest discover -s .claude/skills/perf-compare/scripts\n```\n\nThe tests run offline in a few seconds, against the synthetic Sentry responses in `scripts/fixtures/`, served by a fake transport or by a local `http.server`. They cover:\n\n- query building, time parsing and option validation;\n- cursor pagination, retries, and real-socket errors;\n- the percentile, bootstrap and Fisher maths with fixed seeds;\n- seeded A/A calibration on independent and trace-clustered data;\n- the verdict rules, every exit code, and the missing-token error.\n",".claude/skills/pr-review/SKILL.md":"---\nname: pr-review\ndescription: Review a PR for correctness, security, code quality, and testing issues. TRIGGER when user asks to review a PR, check PR quality, or give feedback on a PR.\nuser-invocable: true\nargs: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Review\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nBefore reading code, understand the **why**, **what**, and **how** from the PR description:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\nEvery PR should have a Why / What / How structure. If any of these are missing, note it as feedback.\n\n## Read the diff\n\n```bash\ngh pr diff {N}\n```\n\n## Fetch existing review comments\n\nBefore posting anything, fetch existing inline comments to avoid duplicates:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\n```\n\n## What to check\n\n**Description quality:** Does the PR description cover Why (motivation/problem), What (summary of changes), and How (approach/implementation details)? If any are missing, request them — you can't judge the approach without understanding the problem and intent.\n\n**Correctness:** logic errors, off-by-one, missing edge cases, race conditions (TOCTOU in file access, credit charging), error handling gaps, async correctness (missing `await`, unclosed resources).\n\n**Security:** input validation at boundaries, no injection (command, XSS, SQL), secrets not logged, file paths sanitized (`os.path.basename()` in error messages).\n\n**Code quality:** apply rules from backend/frontend CLAUDE.md files and root `AGENTS.md`, including its \"User-facing copy\" section (text a person reads says workflow, never agent or graph).\n\n**Architecture:** DRY, single responsibility, modular functions. `Security()` vs `Depends()` for FastAPI auth. `data:` for SSE events, `: comment` for heartbeats. `transaction=True` for Redis pipelines.\n\n**Testing:** edge cases covered, colocated `*_test.py` (backend) / `__tests__/` (frontend), mocks target where symbol is **used** not defined, `AsyncMock` for async.\n\n## Output format\n\nEvery comment **must** be prefixed with `🤖` and a criticality badge:\n\n| Tier | Badge | Meaning |\n|---|---|---|\n| Blocker | `🔴 **Blocker**` | Must fix before merge |\n| Should Fix | `🟠 **Should Fix**` | Important improvement |\n| Nice to Have | `🟡 **Nice to Have**` | Minor suggestion |\n| Nit | `🔵 **Nit**` | Style / wording |\n\nExample: `🤖 🔴 **Blocker**: Missing error handling for X — suggest wrapping in try/except.`\n\n## Post inline comments\n\nFor each finding, post an inline comment on the PR (do not just write a local report):\n\n```bash\n# Get the latest commit SHA for the PR\nCOMMIT_SHA=$(gh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.head.sha')\n\n# Post an inline comment on a specific file/line\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments \\\n  -f body=\"🤖 🔴 **Blocker**: <description>\" \\\n  -f commit_id=\"$COMMIT_SHA\" \\\n  -f path=\"<file path>\" \\\n  -F line=<line number>\n```\n",".claude/skills/open-pr/SKILL.md":"---\nname: open-pr\ndescription: Open a pull request with proper PR template, test coverage, and review workflow. Guides agents through creating a PR that follows repo conventions, ensures existing behaviors aren't broken, covers new behaviors with tests, and handles review via bot when local testing isn't possible. TRIGGER when user asks to \"open a PR\", \"create a PR\", \"make a PR\", \"submit a PR\", \"open pull request\", \"push and create PR\", or any variation of opening/submitting a pull request.\nuser-invocable: true\nargs: \"[base-branch] — optional target branch (defaults to dev).\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Open a Pull Request\n\n## Step 1: Pre-flight checks\n\nBefore opening the PR:\n\n1. Ensure all changes are committed\n2. Ensure the branch is pushed to the remote (`git push -u origin <branch>`)\n3. Run linters/formatters across the whole repo (not just changed files) and commit any fixes\n\n## Step 2: Test coverage\n\n**This is critical.** Before opening the PR, verify:\n\n### Existing behavior is not broken\n- Identify which modules/components your changes touch\n- Run the existing test suites for those areas\n- If tests fail, fix them before opening the PR — do not open a PR with known regressions\n\n### New behavior has test coverage\n- Every new feature, endpoint, or behavior change needs tests\n- If you added a new block, add tests for that block\n- If you changed API behavior, add or update API tests\n- If you changed frontend behavior, verify it doesn't break existing flows\n\nIf you cannot run the full test suite locally, note which tests you ran and which you couldn't in the test plan.\n\n## Step 3: Create the PR using the repo template\n\nRead the canonical PR template at `.github/PULL_REQUEST_TEMPLATE.md` and use it **verbatim** as your PR body:\n\n1. Read the template: `cat .github/PULL_REQUEST_TEMPLATE.md`\n2. Preserve the exact section titles and formatting, including:\n   - `### Why / What / How`\n   - `### Changes 🏗️`\n   - `### Checklist 📋`\n3. Replace HTML comment prompts (`<!-- ... -->`) with actual content; do not leave them in\n4. **Do not pre-check boxes** — leave all checkboxes as `- [ ]` until each step is actually completed\n5. Do not alter the template structure, rename sections, or remove any checklist items\n\n**PR title must use conventional commit format** (e.g., `feat(backend): add new block`, `fix(frontend): resolve routing bug`, `dx(skills): update PR workflow`). See CLAUDE.md for the full list of scopes.\n\nUse `gh pr create` with the base branch (defaults to `dev` if no `[base-branch]` was provided). Use `--body-file` to avoid shell interpretation of backticks and special characters:\n\n```bash\nBASE_BRANCH=\"${BASE_BRANCH:-dev}\"\nPR_BODY=$(mktemp)\ncat > \"$PR_BODY\" << 'PREOF'\n<filled-in template from .github/PULL_REQUEST_TEMPLATE.md>\nPREOF\ngh pr create --base \"$BASE_BRANCH\" --title \"<type>(scope): short description\" --body-file \"$PR_BODY\"\nrm \"$PR_BODY\"\n```\n\n## Step 4: Review workflow\n\n### If you have a workspace that allows testing (docker, running backend, etc.)\n- Run `/pr-test` to do E2E manual testing of the PR using docker compose, agent-browser, and API calls. This is the most thorough way to validate your changes before review.\n- After testing, run `/pr-review` to self-review the PR for correctness, security, code quality, and testing gaps before requesting human review.\n\n### If you do NOT have a workspace that allows testing\nThis is common for agents running in worktrees without a full stack. In this case:\n\n1. Run `/pr-review` locally to catch obvious issues before pushing\n2. **Comment `/review` on the PR** after creating it to trigger the review bot\n3. **Poll for the review** rather than blindly waiting — check for new review comments every 30 seconds using `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate` and the GraphQL inline threads query. The bot typically responds within 30 minutes, but polling lets the agent react as soon as it arrives.\n4. Do NOT proceed or merge until the bot review comes back — but bound the\n   wait at **60 minutes** from the `/review` comment, the same budget\n   `/pr-address` and `/pr-polish` use. If nothing has arrived by then, stop\n   waiting and tell the user the requested review never came. Never block\n   indefinitely on a bot.\n5. Address any issues the bot raises — use `/pr-address` which has a full polling loop with CI + comment tracking\n\n```bash\n# After creating the PR:\nPR_NUMBER=$(gh pr view --json number -q .number)\ngh pr comment \"$PR_NUMBER\" --body \"/review\"\n# Then use /pr-address to poll for and address the review when it arrives\n```\n\n## Step 5: Address review feedback\n\nOnce the review bot or human reviewers leave comments:\n- Run `/pr-address` to address review comments. It will loop until CI is green and all comments are resolved.\n- Do not merge without human approval.\n\n## Related skills\n\n| Skill | When to use |\n|---|---|\n| `/pr-test` | E2E testing with docker compose, agent-browser, API calls — use when you have a running workspace |\n| `/pr-review` | Review for correctness, security, code quality — use before requesting human review |\n| `/pr-address` | Address reviewer comments and loop until CI green — use after reviews come in; it can also post the `/review` trigger itself if the PR has no review yet |\n\n## Step 6: Post-creation\n\nAfter the PR is created and review is triggered:\n- Share the PR URL with the user\n- If waiting on the review bot, let the user know the expected wait time (~30 min)\n- Do not merge without human approval\n",".claude/skills/pr-polish/SKILL.md":"---\nname: pr-polish\ndescription: Alternate /pr-review and /pr-address on a PR until the PR is truly mergeable — no new review findings, zero unresolved inline threads, zero unaddressed top-level reviews or issue comments, all CI checks green, and two consecutive quiet polls after CI settles. Use when the user wants a PR polished to merge-ready without setting a fixed number of rounds.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Polish\n\n**Goal.** Drive a PR to merge-ready by alternating `/pr-review` and `/pr-address` until **all** of the following hold:\n\n1. The most recent `/pr-review` produces **zero new findings** (no new inline comments, no new top-level reviews with a non-empty body).\n2. Every inline review thread reachable via GraphQL reports `isResolved: true`.\n3. Every non-bot, non-author top-level review has been acknowledged (replied-to) OR resolved via a thread it spawned.\n4. Every non-bot, non-author issue comment has been acknowledged (replied-to).\n5. Every CI check is `conclusion: \"success\"` or `\"skipped\"` / `\"neutral\"` — none `\"failure\"` or still pending.\n6. **Two consecutive post-CI polls** (≥60s apart) stay clean — no new threads, no new non-empty reviews, no new issue comments. Bots (coderabbitai, sentry, autogpt-pr-reviewer) frequently post late after CI settles; a single green snapshot is not sufficient.\n7. **No requested-but-unanswered review.** If a `/review` comment was posted on the PR and the review bot has not replied yet, quiet polls do **not** count toward condition 6 — keep polling until the review lands or the wait budget expires. See [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n**Do not stop at a fixed number of rounds.** If round N introduces new comments, round N+1 is required. Cap at `_MAX_ROUNDS = 10` as a safety valve, but expect 2–5 in practice.\n\n## TodoWrite\n\nBefore starting, write two todos so the user can see the loop progression:\n\n- `Round {current}: /pr-review + /pr-address on PR #{N}` — current iteration.\n- `Final polish polling: 2 consecutive clean polls, CI green, 0 unresolved` — runs after the last non-empty review round.\n\nUpdate the `current` round counter at the start of each iteration; mark `completed` only when the round's address step finishes (all new threads addressed + resolved).\n\n## Find the PR\n\n```bash\nARG_PR=\"${ARG:-}\"\n# Normalize URL → numeric ID if the skill arg is a pull-request URL.\nif [[ \"$ARG_PR\" =~ ^https?://github\\.com/[^/]+/[^/]+/pull/([0-9]+) ]]; then\n  ARG_PR=\"${BASH_REMATCH[1]}\"\nfi\nPR=\"${ARG_PR:-$(gh pr list --head \"$(git branch --show-current)\" --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')}\"\nif [ -z \"$PR\" ] || [ \"$PR\" = \"null\" ]; then\n  echo \"No PR found for current branch. Provide a PR number or URL as the skill arg.\"\n  exit 1\nfi\necho \"Polishing PR #$PR\"\n```\n\n## The outer loop\n\n```text\nround = 0\nwhile round < _MAX_ROUNDS:\n    round += 1\n    baseline = snapshot_state(PR)   # see \"Snapshotting state\" below\n    invoke_skill(\"pr-review\", PR)   # posts findings as inline comments / top-level review\n    findings = diff_state(PR, baseline)\n    if findings.total == 0:\n        break  # no new findings → go to polish polling\n    invoke_skill(\"pr-address\", PR)  # resolves every unresolved thread + CI failure\n# Post-loop: polish polling (see below).\npolish_polling(PR)\n```\n\n### Optional: the review bot\n\n`invoke_skill(\"pr-review\", PR)` is the agent's **own** review and always runs every round. In addition to it, the repo's review bot can be summoned on demand by commenting `/review` on the PR (see the `open-pr` skill):\n\n```bash\ngh pr comment \"${PR}\" --body \"/review\"\n```\n\nThis is an **option, not a required step**, and it is **not** a substitute for the `/pr-review` round — use it when you want a second opinion the outer loop cannot produce itself, or when the PR carries no bot findings at all yet. Bot findings land as inline threads and top-level reviews, exactly like the agent's own findings, so both feed the same `invoke_skill(\"pr-address\", PR)` step — no separate handling is needed. If you do request one, the exit conditions must wait for it; see [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n### Snapshotting state\n\nBefore each `/pr-review`, capture a baseline so the diff after the review reflects **only** what the review just added (not pre-existing threads):\n\n```bash\n# Inline threads — total count + latest databaseId per thread\ngh api graphql -f query=\"\n{\n  repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n    pullRequest(number: ${PR}) {\n      reviewThreads(first: 100) {\n        totalCount\n        nodes {\n          id\n          isResolved\n          comments(last: 1) { nodes { databaseId } }\n        }\n      }\n    }\n  }\n}\" > /tmp/baseline_threads.json\n\n# Top-level reviews — count + latest id per non-empty review\ngh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate \\\n  --jq '[.[] | select((.body // \"\") != \"\") | {id, user: .user.login, state, submitted_at}]' \\\n  > /tmp/baseline_reviews.json\n\n# Issue comments — count + latest id per non-bot, non-author comment.\n# Bots are filtered by User.type == \"Bot\" (GitHub sets this for app/bot\n# accounts like coderabbitai, github-actions, sentry-io). The author is\n# filtered by comparing login to the PR author — export it so jq can see it.\nAUTHOR=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}\" --jq '.user.login')\n# Slash-command triggers are excluded here too: a `/review` posted by a\n# maintainer is non-bot and non-author, so without this it enters the baseline\n# and later registers as a \"new finding\", forcing an address round over a\n# comment with nothing to address.\ngh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq --arg author \"$AUTHOR\" \\\n      '[.[][] | select(.user.type != \"Bot\" and .user.login != $author)\n             | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)\n             | {id, user: .user.login, created_at}]' \\\n  > /tmp/baseline_issue_comments.json\n```\n\n### Diffing after a review\n\nAfter `/pr-review` runs, any of these counting as \"new findings\" means another address round is needed:\n\n- New inline thread `id` not in the baseline.\n- An existing thread whose latest comment `databaseId` is higher than the baseline's (new reply on an old thread).\n- A new top-level review `id` with a non-empty body.\n- A new issue comment `id` from a non-bot, non-author user.\n\nIf any of the four buckets is non-empty → not done; invoke `/pr-address` and loop.\n\n## Polish polling\n\nOnce `/pr-review` produces zero new findings, do **not** exit yet. Bots (coderabbitai, sentry, autogpt-pr-reviewer) commonly post late reviews after CI settles — 30–90 seconds after the final push. Poll at 60-second intervals:\n\n```text\nNON_SUCCESS_TERMINAL = {\"failure\", \"cancelled\", \"timed_out\", \"action_required\", \"startup_failure\"}\nclean_polls = 0\nrequired_clean = 2\nwhile clean_polls < required_clean:\n    # 1. CI gate — any terminal non-success conclusion (not just \"failure\")\n    # must trigger /pr-address. \"success\", \"skipped\", \"neutral\" are clean;\n    # anything else (including cancelled, timed_out, action_required) is a\n    # blocker that won't self-resolve.\n    ci = fetch_check_runs(PR)\n    if any ci.conclusion in NON_SUCCESS_TERMINAL:\n        invoke_skill(\"pr-address\", PR)  # address failures + any new comments\n        baseline = snapshot_state(PR)   # reset — push during address invalidates old baseline\n        clean_polls = 0\n        continue\n    if any ci.conclusion is None (still in_progress):\n        sleep 60; continue  # wait without counting this as clean\n\n    # 2. Comment / thread gate\n    threads = fetch_unresolved_threads(PR)\n    new_issue_comments = diff_against_baseline(issue_comments)\n    new_reviews = diff_against_baseline(reviews)\n    if threads or new_issue_comments or new_reviews:\n        invoke_skill(\"pr-address\", PR)\n        baseline = snapshot_state(PR)   # reset — the address loop just dealt with these,\n                                        # otherwise they stay \"new\" relative to the old baseline forever\n        clean_polls = 0\n        continue\n\n    # 3. Mergeability gate\n    mergeable = gh api repos/.../pulls/${PR} --jq '.mergeable'\n    if mergeable == false (CONFLICTING):\n        resolve_conflicts(PR)  # see pr-address skill\n        clean_polls = 0\n        continue\n    if mergeable is null (UNKNOWN):\n        sleep 60; continue\n\n    # 4. Pending-review gate — a /review with no bot answer yet.\n    # Quiet is exactly what a bot still thinking looks like, so don't bank\n    # a clean poll on it. review_requested_but_unanswered() already returns\n    # false once REVIEW_WAIT_BUDGET is spent, so the loop cannot hang here.\n    if review_requested_but_unanswered(PR):\n        sleep 60; continue\n\n    clean_polls += 1\n    sleep 60\n```\n\nOnly after `clean_polls == 2` do you report `ORCHESTRATOR:DONE`.\n\n### Concrete CI fetch (don't parse `gh pr checks` text columns)\n\nThe `fetch_check_runs(PR)` step above must use `--json`, not the default text output. Job names can contain spaces and parentheses (e.g. `test (3.11)`, `Analyze (python)`), so `gh pr checks $PR | awk '{print $2}'` extracts `(3.11)` instead of the status — leading to a clean-poll firing while jobs are still pending.\n\n```bash\n# Reliable: use --json so columns are unambiguous.\nci_json=$(gh pr checks $PR --repo Significant-Gravitas/AutoGPT --json name,state,bucket)\npending=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"pending\")] | length')\nfailed=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"fail\" or .bucket == \"cancel\")] | length')\n\n# Buckets are: pass | fail | pending | cancel | skipping\n# (NOTE: gh pr checks does NOT expose `conclusion` as a JSON field —\n# only `bucket`. Don't confuse with the GitHub REST API's check_runs\n# endpoint, which DOES use conclusion.)\n```\n\nMap back to the pseudocode above: `bucket == \"pending\"` is `ci.conclusion is None (still in_progress)`; `bucket in {\"fail\", \"cancel\"}` is `ci.conclusion in NON_SUCCESS_TERMINAL`; `bucket in {\"pass\", \"skipping\"}` is clean.\n\n### Waiting on a requested bot review\n\n`autogpt-pr-reviewer[bot]` typically takes ~30 minutes to answer a `/review` — far longer than the 60-second poll interval, so without this gate two clean polls would report `ORCHESTRATOR:DONE` while the requested review is still in flight.\n\n`review_requested_but_unanswered(PR)` is:\n\nRecompute from the API on **every** poll — never track elapsed time in a loop\nvariable, which is how this gate becomes an infinite wait. `--paginate`\nconcatenates one array per page, so `last` picks the last item of the final\npage rather than the newest overall; use `--slurp` and `max_by`. An answer is\n*either* a top-level review or an inline review comment, whichever is later —\nan inline-only reply is still an answer, and counting only top-level reviews\nleft the gate waiting after its threads had already been addressed.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\n`REVIEW_WAIT_BUDGET = 60 minutes`, measured as `now - REQUESTED_AT`:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\nAn observed reply on this repo took **46 minutes**, so a 45-minute budget\nwould have abandoned a review that was about to arrive. 60 gives headroom.\n\nPast the budget, stop waiting, resume counting clean polls normally, and note\nin the final report that the requested bot review never arrived. The loop must\nnever hang on a bot.\n\n**Never post a second `/review` while one is unanswered.** Check\n`review_requested_but_unanswered(PR)` before triggering, not just before\nexiting — otherwise each round requests another review and the bot answers a\nqueue of duplicates.\n\n### Why 2 clean polls, not 1\n\nA single green snapshot can be misleading — the final CI check often completes ~30s before a bot posts its delayed review. One quiet cycle does not prove the PR is stable; two consecutive cycles with no new threads, reviews, or issue comments arriving gives high confidence nothing else is incoming.\n\n### Why checking every source each poll\n\n`/pr-address` polling inside a single round already re-checks its own comments, but `/pr-polish` sits a level above and must also catch:\n\n- New top-level reviews (autogpt-pr-reviewer sometimes posts structured feedback only after several CI green cycles).\n- Issue comments from human reviewers (not caught by inline thread polling).\n- Sentry bug predictions that land on new line numbers post-push.\n- Merge conflicts introduced by a race between your push and a merge to `dev`.\n\n## Invocation pattern\n\nDelegate to existing skills with the `Skill` tool; do not re-implement the review or address logic inline. This keeps the polish loop focused on orchestration and lets the child skills evolve independently.\n\n```python\nSkill(skill=\"pr-review\",  args=pr_url)\nSkill(skill=\"pr-address\", args=pr_url)\n```\n\nAfter each child invocation, re-query GitHub state directly — never trust a summary for the stop condition. The orchestrator's `ORCHESTRATOR:DONE` is verified against actual GraphQL / REST responses per the rules in `pr-address`'s \"Verify actual count before outputting ORCHESTRATOR:DONE\" section.\n\n### **Auto-continue: do NOT end your response between child skills**\n\n`/pr-polish` is a single orchestration task — one invocation drives the PR all the way to merge-ready. When a child `Skill()` call returns control to you:\n\n- Do NOT summarize and stop.\n- Do NOT wait for user confirmation to continue.\n- Immediately, in the same response, perform the next loop step: state diff → decide next action → next `Skill()` call or polling sleep.\n\nThe child skill returning is a **loop iteration boundary**, not a conversation turn boundary. You are expected to keep going until one of the exit conditions in the opening section is met (2 consecutive clean polls, `_MAX_ROUNDS` hit, or an unrecoverable error).\n\nIf the user needs to approve a risky action mid-loop (e.g., a force-push or a destructive git operation), pause there — but not at the routine \"round N finished, round N+1 needed\" boundary. Those are silent transitions.\n\n### **Run /pr-polish in the foreground — never in a background agent**\n\nSpawning `/pr-polish` inside an `Agent(subagent_type=\"general-purpose\")` background task **does not work**. Background agents don't inherit the parent's slash-command registry, so `Skill(skill=\"pr-review\")` and `Skill(skill=\"pr-address\")` calls aren't available — the agent has to manually replicate the child skills' logic, which is fragile and tends to stall on the first network or rate-limit hiccup. Symptom: the background task reports `stalled: no progress for 600s` mid-review.\n\nRun `/pr-polish` inline in the foreground conversation. If the user asks for \"/pr-polish + /pr-test in parallel\", split them: foreground `/pr-polish`, and ONLY then can the test step go to a background agent (because `/pr-test` doesn't itself need to invoke skills).\n\n### **You MUST invoke `Skill(pr-review)` every round — even when bot reviews already exist**\n\nA common failure mode: CodeRabbit / autogpt-pr-reviewer / Sentry have already posted findings on the PR, and the orchestrator skips the `Skill(pr-review)` step on the assumption that \"review has been done.\" That's wrong — the outer loop's purpose is to layer **the agent's own review** on top of the bot reviews, catching issues the bots miss (architecture, naming, cross-file invariants, hidden coupling). If the orchestrator only addresses bot findings without ever running its own review, the loop converges to \"bot-clean\" but not \"agent-reviewed-clean,\" and the user reasonably asks \"did /pr-polish even read the diff?\"\n\n**Self-check before reporting `ORCHESTRATOR:DONE`:** confirm at least one `Skill(skill=\"pr-review\")` call appears in the current orchestration. If none, the loop is incomplete — go back and run one round.\n\n## GitHub rate limits\n\nThis skill issues many GraphQL calls (one review-thread query per outer iteration plus per-poll queries inside polish polling). Expect the GraphQL budget to be tight on large PRs. When `gh api rate_limit --jq .resources.graphql.remaining` drops below ~200, back off:\n\n- Fall back to REST for reads (flat `/pulls/{N}/comments`, `/pulls/{N}/reviews`, `/issues/{N}/comments`) per the `pr-address` skill's GraphQL-fallback section.\n- Queue thread resolutions (GraphQL-only) until the budget resets; keep making progress on fixes + REST replies meanwhile.\n- `sleep 5` between any batch of ≥20 writes to avoid secondary rate limits.\n\n## Safety valves\n\n- `_MAX_ROUNDS = 10` — if review+address rounds exceed this, stop and escalate to the user with a summary of what's still unresolved. A PR that cannot converge in 10 rounds has systemic issues that need human judgment.\n- After each commit, run `poetry run format` / `pnpm format && pnpm lint && pnpm types` per the target codebase's conventions. A failing format check is CI `failure` that will never self-resolve.\n- Every `/pr-review` round checks for **duplicate** concerns first (via `pr-review`'s own \"Fetch existing review comments\" step) so the loop does not re-post the same finding that a prior round already resolved.\n\n## Reporting\n\nWhen the skill finishes (either via two clean polls or hitting `_MAX_ROUNDS`), produce a compact summary:\n\n```\nPR #{N} polish complete ({rounds_completed} rounds):\n- {X} inline threads opened and resolved\n- {Y} CI failures fixed\n- {Z} new commits pushed\nFinal state: CI green, {total} threads all resolved, mergeable.\n```\n\nIf exiting via `_MAX_ROUNDS`, flag explicitly:\n\n```\nPR #{N} polish stopped at {_MAX_ROUNDS} rounds — NOT merge-ready:\n- {N} threads still unresolved: {titles}\n- CI status: {summary}\nNeeds human review.\n```\n\n## When to use this skill\n\nUse when the user says any of:\n- \"polish this PR\"\n- \"keep reviewing and addressing until it's mergeable\"\n- \"loop /pr-review + /pr-address until done\"\n- \"make sure the PR is actually merge-ready\"\n\nDo **not** use when:\n- User wants just one review pass (→ `/pr-review`).\n- User wants to address already-posted comments without further self-review (→ `/pr-address`).\n- A fixed round count is explicitly requested (e.g., \"do 3 rounds\") — honour the count instead of converging.\n",".claude/skills/pr-address/SKILL.md":"---\nname: pr-address\ndescription: Address PR review comments and loop until CI green and all comments resolved. TRIGGER when user asks to address comments, fix PR feedback, respond to reviewers, or babysit/monitor a PR.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Address\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nUnderstand the **Why / What / How** before addressing comments — you need context to make good fixes:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\n> If GraphQL is rate-limited, `gh pr view` fails. See [GitHub rate limits](#github-rate-limits) for REST fallbacks.\n\n## Optional: trigger the review bot\n\n**Only relevant if the PR has no review yet.** If the fetch steps below turn up no reviews and no inline threads, there is nothing to address *yet* — that is not the same as being done. You can summon the repo's review bot yourself:\n\n```bash\ngh pr comment {N} --body \"/review\"\n```\n\nThis is an **option, not a required step**. Skip it whenever the PR already has reviews (bot or human) — those are what you are here to address. See the `open-pr` skill's \"Review workflow\" step for the same trigger in the PR-creation flow.\n\n`autogpt-pr-reviewer[bot]` typically responds within ~30 minutes. **Poll for it rather than sleeping** — the loop in [Polling for CI + new comments](#polling-for-ci--new-comments) already re-checks every source every 30 seconds, so run that loop and react the moment the review lands. Note the `id` and `created_at` of the `/review` comment you posted; [Waiting on a requested review](#waiting-on-a-requested-review) needs them for the exit condition.\n\n## Fetch comments (all sources)\n\n### 1. Inline review threads — GraphQL (primary source of actionable items)\n\n> ⚠️ **WARNING — PAGINATE ALL PAGES BEFORE ADDRESSING ANYTHING**\n>\n> `reviewThreads(first: 100)` returns at most 100 threads per page AND returns threads **oldest-first**. On a PR with many review cycles (e.g. 373 threads), the oldest 100–200 threads are from past cycles and are **all already resolved**. Filtering client-side with `select(.isResolved == false)` on page 1 therefore yields **0 results** — even though pages 2–4 contain many unresolved threads from recent review cycles.\n>\n> **This is the most common failure mode:** agent fetches page 1, sees 0 unresolved after filtering, stops pagination, reports \"done\" — while hundreds of unresolved threads sit on later pages.\n>\n> One observed PR had 142 total threads: page 1 returned 0 unresolved (all old/resolved), while pages 2–3 had 111 unresolved. Another with 373 threads across 4 pages also had page 1 entirely resolved.\n>\n> **The rule: ALWAYS paginate to `hasNextPage == false` regardless of the per-page unresolved count. Never stop early because a page returns 0 unresolved.**\n\n**Step 1 — Fetch total count and sanity-check the newest threads:**\n\n```bash\n# Get total count and the newest 100 threads (last: 100 returns newest-first)\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n      newest: reviewThreads(last: 100) {\n        nodes { isResolved }\n      }\n    }\n  }\n}' | jq '{ total: .data.repository.pullRequest.reviewThreads.totalCount, newest_unresolved: [.data.repository.pullRequest.newest.nodes[] | select(.isResolved == false)] | length }'\n```\n\nIf `total > 100`, you have multiple pages — you **must** paginate all of them regardless of what `newest_unresolved` shows. The `last: 100` check is a sanity signal only; the full loop below is mandatory.\n\n**Step 2 — Collect all unresolved thread IDs across all pages:**\n\n```bash\n# Accumulate all unresolved threads — loop until hasNextPage == false\nCURSOR=\"\"\nALL_THREADS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            id\n            isResolved\n            path\n            line\n            comments(last: 1) {\n              nodes { databaseId body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  # Append unresolved nodes from this page\n  PAGE_THREADS=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)]')\n  ALL_THREADS=$(echo \"$ALL_THREADS $PAGE_THREADS\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\n\n# Reverse so newest threads (last pages) are addressed first — GitHub returns oldest-first\n# and the most recent review cycle's comments are the ones blocking approval.\nALL_THREADS=$(echo \"$ALL_THREADS\" | jq 'reverse')\n\necho \"Total unresolved threads: $(echo \"$ALL_THREADS\" | jq 'length')\"\necho \"$ALL_THREADS\" | jq '[.[] | {id, path, line, body: .comments.nodes[0].body[:200]}]'\n```\n\n**Step 3 — Address every thread in `ALL_THREADS`, then resolve.**\n\nOnly after this loop completes (all pages fetched, count confirmed) should you begin making fixes.\n\n> **Why reverse?** GraphQL returns threads oldest-first and exposes no `orderBy` option. A PR with 373 threads has ~4 pages; threads from the latest review cycle land on the last pages. Processing in reverse ensures the newest, most blocking comments are addressed first — the earlier pages mostly contain outdated threads from prior cycles.\n\n**Filter to unresolved threads only** — skip any thread where `isResolved: true`. `comments(last: 1)` returns the most recent comment in the thread — act on that; it reflects the reviewer's final ask. Use the thread `id` (Relay global ID) to track threads across polls.\n\n> If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the REST fallback (flat comment list — no thread grouping or `isResolved`).\n\n### 2. Top-level reviews — REST (MUST paginate)\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits or outages. Continue polling reviews normally even when GraphQL is exhausted.**\n\n**CRITICAL — always `--paginate`.** Reviews default to 30 per page. PRs can have 80–170+ reviews (mostly empty resolution events). Without pagination you miss reviews past position 30 — including `autogpt-pr-reviewer[bot]`'s structured review which is typically posted after several CI runs and sits well beyond the first page.\n\nTwo things to extract:\n- **Overall state**: look for `CHANGES_REQUESTED` or `APPROVED` reviews.\n- **Actionable feedback**: non-empty bodies only. Empty-body reviews are thread-resolution events — they indicate progress but have no feedback to act on.\n\n**Where each reviewer posts:**\n- `autogpt-pr-reviewer[bot]` — posts detailed structured reviews (\"Blockers\", \"Should Fix\", \"Nice to Have\") as **top-level reviews**. Not present on every PR. Address ALL items.\n- `sentry[bot]` — posts bug predictions as **inline threads**. Fix real bugs, explain false positives.\n- `coderabbitai[bot]` — posts summaries as **top-level reviews** AND actionable items as **inline threads**. Address actionable items.\n- Human reviewers — can post in any source. Address ALL non-empty feedback.\n\n### 3. PR conversation comments — REST\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits.**\n\nMostly contains: bot summaries (`coderabbitai[bot]`), CI/conflict detection (`github-actions[bot]`), and author status updates. Scan for non-empty messages from non-bot human reviewers that aren't the PR author — those are the ones that need a response.\n\n**Slash-command trigger comments are never actionable items — skip them.** A comment whose entire body is a slash command (`/review` being the common one) exists to summon the review bot; it is not a request directed at you. Do not reply to it, do not treat it as feedback, and do not count it as a \"new comment\" that restarts the loop. The non-author filter above happens to hide the usual case where the PR author posted it, but that is incidental — a maintainer can post `/review` on someone else's PR, and it must still be skipped. The actionable content is the review it produces, which arrives as a top-level review and/or inline threads, not as a conversation comment.\n\n## For each unaddressed comment\n\n**CRITICAL: The only valid sequence is fix → commit → push → reply → resolve. Never resolve a thread without a real code commit.**\n\nResolving a thread via `resolveReviewThread` without an actual fix is the most common failure mode — it makes unresolved counts drop without any real change, producing a false \"done\" signal. If the issue was genuinely a false positive (no code change needed), reply explaining why and then resolve. Otherwise:\n\nAddress comments **one at a time**: fix → commit → push → inline reply → resolve.\n\n1. Read the referenced code, make the fix (or reply explaining why it's not needed)\n2. Commit and push the fix\n3. Reply **inline** (not as a new top-level comment) referencing the fixing commit — this is what resolves the conversation for bot reviewers (coderabbitai, sentry):\n\nUse a **markdown commit link** so GitHub renders it as a clickable reference. Always get the full SHA with `git rev-parse HEAD` **after** committing — never copy a SHA from a previous commit or hardcode one:\n\n```bash\nFULL_SHA=$(git rev-parse HEAD)\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies \\\n  -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): <description>\"\n```\n\n| Comment type | How to reply |\n|---|---|\n| Inline review (`pulls/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n| Conversation (`issues/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n\n### What counts as a valid resolution\n\nOnly two situations justify calling `resolveReviewThread`:\n\n1. **Real code fix**: you changed the code, committed + pushed, and replied with the SHA. The commit diff must actually address the concern — not just touch the same file.\n2. **Genuine false positive**: the reviewer's concern does not apply to this code, and you can give a specific technical reason (e.g. \"Not applicable — `sdk_cwd` is pre-validated by `_make_sdk_cwd()` which applies normpath + prefix assertion before reaching this point\").\n\n**Anti-patterns that look resolved but aren't — never do these:**\n- `\"Accepted, tracked as follow-up\"` — a deferral, not a fix. The concern is still open. Do not resolve.\n- `\"Acknowledged\"` or `\"Same as above\"` — these are acknowledgements, not fixes. Do not resolve.\n- `\"Fixed in abc1234\"` where `abc1234` is a commit that doesn't actually change the flagged line/logic — dishonest. Verify `git show abc1234 -- path/to/file` changes the right thing before posting.\n- Resolving without replying — the reviewer never sees what happened.\n\nWhen in doubt: if a code change is needed, make it. A deferred issue means the thread stays open until the follow-up PR is merged.\n\n## Codecov coverage\n\nCodecov patch target is **80%** on changed lines. Checks are **informational** (not blocking) but should be green.\n\n### Running coverage locally\n\n**Backend** (from `autogpt_platform/backend/`):\n```bash\npoetry run pytest -s -vv --cov=backend --cov-branch --cov-report term-missing\n```\n\n**Frontend** (from `autogpt_platform/frontend/`):\n```bash\npnpm vitest run --coverage\n```\n\n### When codecov/patch fails\n\n1. Find uncovered files: `git diff --name-only $(gh pr view --json baseRefName --jq '.baseRefName')...HEAD`\n2. For each uncovered file — extract inline logic to `helpers.ts`/`helpers.py` and test those (highest ROI). Colocate tests as `*_test.py` (backend) or `__tests__/*.test.ts` (frontend).\n3. Run coverage locally to verify, commit, push.\n\n## Format and commit\n\nAfter fixing, format the changed code:\n\n- **Backend** (from `autogpt_platform/backend/`): `poetry run format`\n- **Frontend** (from `autogpt_platform/frontend/`): `pnpm format && pnpm lint && pnpm types`\n\nIf API routes changed, regenerate the frontend client:\n```bash\ncd autogpt_platform/backend && poetry run rest &\nREST_PID=$!\ntrap \"kill $REST_PID 2>/dev/null\" EXIT\nWAIT=0; until curl -sf http://localhost:8006/health > /dev/null 2>&1; do sleep 1; WAIT=$((WAIT+1)); [ $WAIT -ge 60 ] && echo \"Timed out\" && exit 1; done\ncd ../frontend && pnpm generate:api:force\nkill $REST_PID 2>/dev/null; trap - EXIT\n```\nNever manually edit files in `src/app/api/__generated__/`.\n\nThen commit and **push immediately** — never batch commits without pushing. Each fix should be visible on GitHub right away so CI can start and reviewers can see progress.\n\n**Never push empty commits** (`git commit --allow-empty`) to re-trigger CI or bot checks. When a check fails, investigate the root cause (unchecked PR checklist, unaddressed review comments, code issues) and fix those directly. Empty commits add noise to git history.\n\nFor backend commits in worktrees: `poetry run git commit` (pre-commit hooks).\n\n## Coverage\n\nCodecov enforces patch coverage on new/changed lines — new code you write must be tested. Before pushing, verify you haven't left new lines uncovered:\n\n```bash\ncd autogpt_platform/backend\npoetry run pytest --cov=. --cov-report=term-missing {path/to/changed/module}\n```\n\nLook for lines marked `miss` — those are uncovered. Add tests for any new code you wrote as part of addressing comments.\n\n**Rules:**\n- New code you add should have tests\n- Don't remove existing tests when fixing comments\n- If a reviewer asks you to delete code, also delete its tests, but verify coverage hasn't dropped on remaining lines\n\n## The loop\n\n```text\naddress comments → format → commit → push\n→ wait for CI (while addressing new comments) → fix failures → push\n→ re-check comments after CI settles\n→ repeat until: all comments addressed AND CI green AND no new comments arriving\n```\n\n### Polling for CI + new comments\n\nAfter pushing, poll for **both** CI status and new comments in a single loop. Do not use `gh pr checks --watch` — it blocks the tool and prevents reacting to new comments while CI is running.\n\n> **Note:** `gh pr checks --watch --fail-fast` is tempting but it blocks the entire Bash tool call, meaning the agent cannot check for or address new comments until CI fully completes. Always poll manually instead.\n\n**Polling loop — repeat every 30 seconds:**\n\n1. Check CI status:\n```bash\ngh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,name,link\n```\n   Parse the results: if every check has `bucket` of `\"pass\"` or `\"skipping\"`, CI is green. If any has `\"fail\"`, CI has failed. Otherwise CI is still pending.\n\n2. Check for merge conflicts:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json mergeable --jq '.mergeable'\n```\n   If the result is `\"CONFLICTING\"`, the PR has a merge conflict — see \"Resolving merge conflicts\" below. If `\"UNKNOWN\"`, GitHub is still computing mergeability — wait and re-check next poll.\n\n3. Check for new/changed comments (all three sources):\n\n   **Inline threads** — re-run the GraphQL query from \"Fetch comments\". For each unresolved thread, record `{thread_id, last_comment_databaseId}` as your baseline. On each poll, action is needed if:\n   - A new thread `id` appears that wasn't in the baseline (new thread), OR\n   - An existing thread's `last_comment_databaseId` has changed (new reply on existing thread)\n\n   **Conversation comments:**\n   ```bash\n   # Exclude slash-command triggers here, not just when reading — otherwise a\n   # `/review` (yours or a maintainer's) counts as a \"new comment\" and forces\n   # an address round over a comment with nothing to address.\n   gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate --slurp \\\n     | jq '[.[][] | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)]'\n   ```\n   Compare total count and newest `id` against baseline. Filter to non-empty, non-bot, non-author-update messages.\n\n   **Top-level reviews:**\n   ```bash\n   gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n   ```\n   Watch for new non-empty reviews (`CHANGES_REQUESTED` or `COMMENTED` with body). Compare total count and newest `id` against baseline.\n\n4. **React in this precedence order (first match wins):**\n\n| What happened | Action |\n|---|---|\n| Merge conflict detected | See \"Resolving merge conflicts\" below. |\n| Mergeability is `UNKNOWN` | GitHub is still computing mergeability. Sleep 30 seconds, then restart polling from the top. |\n| New comments detected | Address them (fix → commit → push → reply). After pushing, re-fetch all comments to update your baseline, then restart this polling loop from the top (new commits invalidate CI status). |\n| CI failed (bucket == \"fail\") | Get failed check links: `gh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,link --jq '.[] \\| select(.bucket == \"fail\") \\| .link'`. Extract run ID from link (format: `.../actions/runs/<run-id>/job/...`), read logs with `gh run view <run-id> --repo Significant-Gravitas/AutoGPT --log-failed`. Fix → commit → push → restart polling. |\n| CI green + no new comments, but a requested `/review` is unanswered | The bot is still working — quiet polls are exactly what that looks like. Keep polling; do **not** count these as the quiet polls below. See \"Waiting on a requested review\". |\n| CI green + no new comments | **Do not exit immediately.** Bots (coderabbitai, sentry) often post reviews shortly after CI settles. Continue polling for **2 more cycles (60s)** after CI goes green. Only exit after 2 consecutive green+quiet polls. |\n| CI pending + no new comments | Sleep 30 seconds, then poll again. |\n\n**The loop ends when:** CI fully green + all comments addressed + **2 consecutive polls with no new comments after CI settled** + no requested-but-unanswered `/review` (see below).\n\n### Waiting on a requested review\n\nIf a `/review` comment was posted on this PR and the bot has not answered it yet, the \"2 consecutive green+quiet polls\" exit is **premature** — a pending bot review looks identical to a quiet PR. Keep polling until one of these is true:\n\n- **The review arrived.** An answer is *either* a top-level review or an inline review comment from `autogpt-pr-reviewer[bot]`, whichever is later — an inline-only reply is still an answer, and counting only top-level reviews leaves the gate waiting after its threads have already been addressed. Once it lands, treat it as \"New comments detected\" and address it normally.\n- **The budget ran out.** Stop waiting **60 minutes** after the `/review` comment's `created_at`. Then exit per the normal rule and tell the user the requested review never arrived. Never hang indefinitely on a bot.\n\nRecompute both timestamps from the API on every poll rather than tracking\nelapsed time in a loop variable — a counter that some branch forgets to\nincrement is how this gate becomes an infinite wait.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\nA `/review` posted before this session still counts. Budget is **60 minutes**\nfrom `REQUESTED_AT`: an observed reply on this repo took 46 minutes, so 45\nwould have abandoned a review that was about to land. Enforce it in the same\npoll so the loop cannot wait forever:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\n**Do not post another `/review` while one is unanswered** — check this same\ncondition before triggering, or each round queues a duplicate request.\n\n### Resolving merge conflicts\n\n1. Identify the PR's target branch and remote:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json baseRefName --jq '.baseRefName'\ngit remote -v   # find the remote pointing to Significant-Gravitas/AutoGPT (typically 'upstream' in forks, 'origin' for direct contributors)\n```\n\n2. Pull the latest base branch with a 3-way merge:\n```bash\ngit pull {base-remote} {base-branch} --no-rebase\n```\n\n3. Resolve conflicting files, then verify no conflict markers remain:\n```bash\nif grep -R -n -E '^(<<<<<<<|=======|>>>>>>>)' <conflicted-files>; then\n  echo \"Unresolved conflict markers found — resolve before proceeding.\"\n  exit 1\nfi\n```\n\n4. Stage and push:\n```bash\ngit add <conflicted-files>\ngit commit -m \"Resolve merge conflicts with {base-branch}\"\ngit push\n```\n\n5. Restart the polling loop from the top — new commits reset CI status.\n\n## GitHub rate limits\n\nThree distinct rate limits exist — they have different causes, error shapes, and recovery times:\n\n| Error | HTTP code | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `{\"message\":\"API rate limit exceeded\"}` | 429 | Primary REST rate limit — 5000 calls/hr per user | Wait until `X-RateLimit-Reset` header timestamp |\n| `GraphQL: API rate limit already exceeded for user ID ...` | 403 on stderr, `gh` exits 1 | **GraphQL-specific** per-user limit — distinct from REST's 5000/hr and from the abuse secondary limit. Trips faster than REST because point costs per query. | Wait until the GraphQL window resets (typically ~1 hour from the first call in the window). REST still works — use fallbacks below. |\n\n**Prevention:** Add `sleep 3` between individual thread reply API calls. When posting >20 replies, increase to `sleep 5`.\n\n### Detection\n\nThe `gh` CLI surfaces the GraphQL limit on stderr with the exact string `GraphQL: API rate limit already exceeded for user ID <id>` and exits 1 — any `gh api graphql ...` **or** `gh pr view ...` call fails. Check current quota and reset time via the REST endpoint that reports GraphQL quota (this call is REST and still works whether GraphQL is rate-limited OR fully down):\n\n```bash\ngh api rate_limit --jq '.resources.graphql'   # { \"limit\": 5000, \"used\": 5000, \"remaining\": 0, \"reset\": 1729...}\n# Human-readable reset:\ngh api rate_limit --jq '.resources.graphql.reset' | xargs -I{} date -r {}\n```\n\nRetry when `remaining > 0`. If you need to proceed sooner, sleep 2–5 min and probe again — the limit is per user, not per machine, so other concurrent agents under the same token also consume it.\n\n### What keeps working\n\nWhen GraphQL is unavailable (rate-limited or outage):\n\n- **Keeps working (REST):** top-level reviews fetch, conversation comments fetch, all inline-comment replies, CI status (`gh pr checks`), and the `gh api rate_limit` probe.\n- **Degraded:** inline thread list — fall back to flat `/pulls/{N}/comments` REST, which drops thread grouping, `isResolved`, and Relay thread IDs. You still get comment bodies and the `databaseId` as `id`, enough to read and reply.\n- **Blocked:** `gh pr view`, the `resolveReviewThread` mutation, and any new `gh api graphql` queries — wait for the quota to reset.\n\n### Fall back to REST\n\n**PR metadata reads** — `gh pr view` uses GraphQL under the hood; use the REST pulls endpoint instead, which returns the full PR object:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.body'           # == --json body\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.base.ref'       # == --json baseRefName\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.mergeable'      # == --json mergeable\n```\n\nNote: REST `mergeable` returns `true|false|null`; GraphQL returns `MERGEABLE|CONFLICTING|UNKNOWN`. The `null` case maps to `UNKNOWN` — treat it the same (still computing; poll again).\n\n**Inline comments (flat list)** — no thread grouping or `isResolved`, but enough to read and reply:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate \\\n  | jq '[.[] | {id, path, line, user: .user.login, body: .body[:200], in_reply_to_id}]'\n```\n\nUse this degraded mode to make progress on the fix → reply loop, then return to GraphQL for `resolveReviewThread` once the rate limit resets.\n\n**Replies** — already REST-native (`/pulls/{N}/comments/{ID}/replies`); no change needed, use the same command as the main flow.\n\n**`resolveReviewThread`** — **no REST equivalent**; GitHub does not expose a REST endpoint for thread resolution. Queue the thread IDs needing resolution, wait for the GraphQL limit to reset, then run the resolve mutations in a batch (with `sleep 3` between calls, per the secondary-limit guidance).\n\n### Recovery from secondary rate limit (403 abuse)\n\n1. Stop all API writes immediately\n2. Wait **2 minutes minimum** (not 60s — secondary limits are stricter)\n3. Resume with `sleep 3` between each call\n4. If 403 persists after 2 min, wait another 2 min before retrying\n\nNever batch all replies in a tight loop — always space them out.\n\n## Parallel thread resolution\n\nWhen a PR has more than 10 unresolved threads, addressing one commit per thread is slow. Use this strategy instead:\n\n### Group by file, batch per commit\n\n1. Sort `ALL_THREADS` by `path` — threads in the same file can share a single commit.\n2. Fix all threads in one file → `git commit` → `git push` → reply to **all** those threads with the same SHA → resolve them all.\n3. Move to the next file group and repeat.\n\nThis reduces N commits to (number of files touched), which is usually 3–5 instead of 15–30.\n\n### Posting replies concurrently (for large batches)\n\nFor truly independent thread groups (different files, no shared logic), you can post replies in parallel using background subshells — but always space out API writes:\n\n```bash\n# Post replies to a batch of threads concurrently, 3s apart\n(\n  sleep 3\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID1}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\n(\n  sleep 6\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID2}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\nwait  # wait for all background replies before resolving\n```\n\nThen resolve sequentially (GraphQL mutations):\n```bash\nfor THREAD_ID in \"$THREAD1\" \"$THREAD2\" \"$THREAD3\"; do\n  gh api graphql -f query=\"mutation { resolveReviewThread(input: {threadId: \\\"${THREAD_ID}\\\"}) { thread { isResolved } } }\"\n  sleep 3\ndone\n```\n\n**Always sleep 3s between individual API writes** — GitHub's secondary rate limit (403) triggers on bursts of >20 writes. Increase to `sleep 5` when posting more than 20 replies in a batch.\n\n## Resolving threads via GraphQL\n\nUse `resolveReviewThread` **only after** the commit is pushed and the reply is posted:\n\n```bash\ngh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"THREAD_ID\"}) { thread { isResolved } } }'\n```\n\n**Never call this mutation before committing the fix.** The orchestrator will verify actual unresolved counts via GraphQL after you output `ORCHESTRATOR:DONE` — false resolutions will be caught and you will be re-briefed.\n\n> `resolveReviewThread` is GraphQL-only — no REST equivalent. If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the queue-and-retry flow.\n\n### Verify actual count before outputting ORCHESTRATOR:DONE\n\nBefore claiming \"0 unresolved threads\", always query GitHub directly — don't rely on your own bookkeeping. Paginate all pages — a single `first: 100` query misses threads beyond page 1:\n\n```bash\n# Step 1: get total thread count\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n    }\n  }\n}' | jq '.data.repository.pullRequest.reviewThreads.totalCount'\n\n# Step 2: paginate all pages, count truly unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes { isResolved }\n        }\n      }\n    }\n  }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved threads: $UNRESOLVED\"\n```\n\nOnly output `ORCHESTRATOR:DONE` after this loop reports 0.\n",".claude/skills/orchestrate/SKILL.md":"---\nname: orchestrate\ndescription: \"Meta-agent supervisor that manages a fleet of Claude Code agents running in tmux windows. Auto-discovers spare worktrees, spawns agents, monitors state, kicks idle agents, approves safe confirmations, and recycles worktrees when done. TRIGGER when user asks to supervise agents, run parallel tasks, manage worktrees, check agent status, or orchestrate parallel work.\"\nuser-invocable: true\nargument-hint: \"any free text — e.g. 'start 3 agents on X Y Z', 'show status', 'add task: implement feature A', 'stop', 'how many are free?'\"\nmetadata:\n  author: autogpt-team\n  version: \"6.0.0\"\n---\n\n# Orchestrate — Agent Fleet Supervisor\n\nOne tmux session, N windows — each window is one agent working in its own worktree. Speak naturally; Claude maps your intent to the right scripts.\n\n## Scripts\n\n```bash\nSKILLS_DIR=$(git rev-parse --show-toplevel)/.claude/skills/orchestrate/scripts\nSTATE_FILE=~/.claude/orchestrator-state.json\n```\n\n| Script | Purpose |\n|---|---|\n| `find-spare.sh [REPO_ROOT]` | List free worktrees — one `PATH BRANCH` per line |\n| `spawn-agent.sh SESSION PATH SPARE NEW_BRANCH OBJECTIVE [PR_NUMBER] [STEPS...]` | Create window + checkout branch + launch claude + send task. **Stdout: `SESSION:WIN` only** |\n| `recycle-agent.sh WINDOW PATH SPARE_BRANCH` | Kill window + restore spare branch |\n| `run-loop.sh` | **Mechanical babysitter** — idle restart + dialog approval + recycle on ORCHESTRATOR:DONE + supervisor health check + all-done notification |\n| `verify-complete.sh WINDOW` | Verify PR is done: checkpoints ✓ + 0 unresolved threads + CI green + no fresh CHANGES_REQUESTED. Repo auto-derived from state file `.repo` or git remote. |\n| `notify.sh MESSAGE` | Send notification via Discord webhook (env `DISCORD_WEBHOOK_URL` or state `.discord_webhook`), macOS notification center, and stdout |\n| `capacity.sh [REPO_ROOT]` | Print available + in-use worktrees |\n| `status.sh` | Print fleet status + live pane commands |\n| `poll-cycle.sh` | One monitoring cycle — classifies panes, tracks checkpoints, returns JSON action array |\n| `classify-pane.sh WINDOW` | Classify one pane state |\n\n## Supervision model\n\n```\nOrchestrating Claude (this Claude session — IS the supervisor)\n  └── Reads pane output, checks CI, intervenes with targeted guidance\n        run-loop.sh (separate tmux window, every 30s)\n          └── Mechanical only: idle restart, dialog approval, recycle on ORCHESTRATOR:DONE\n```\n\n**You (the orchestrating Claude)** are the supervisor. After spawning agents, stay in this conversation and actively monitor: poll each agent's pane every 2-3 minutes, check CI, nudge stalled agents, and verify completions. Do not spawn a separate supervisor Claude window — it loses context, is hard to observe, and compounds context compression problems.\n\n**run-loop.sh** is the mechanical layer — zero tokens, handles things that need no judgment: restart crashed agents, press Enter on dialogs, recycle completed worktrees (only after `verify-complete.sh` passes).\n\n## Checkpoint protocol\n\nAgents output checkpoints as they complete each required step:\n\n```\nCHECKPOINT:<step-name>\n```\n\nRequired steps are passed as args to `spawn-agent.sh` (e.g. `pr-address pr-test`). `run-loop.sh` will not recycle a window until all required checkpoints are found in the pane output. If `verify-complete.sh` fails, the agent is re-briefed automatically.\n\n## Worktree lifecycle\n\n```text\nspare/N branch  →  spawn-agent.sh (--session-id UUID)  →  window + feat/branch + claude running\n                                                                 ↓\n                                               CHECKPOINT:<step> (as steps complete)\n                                                                 ↓\n                                                        ORCHESTRATOR:DONE\n                                                                 ↓\n                                    verify-complete.sh: checkpoints ✓ + 0 threads + CI green + no fresh CHANGES_REQUESTED\n                                                                 ↓\n                                              state → \"done\", notify, window KEPT OPEN\n                                                                 ↓\n                              user/orchestrator explicitly requests recycle\n                                                                 ↓\n                                         recycle-agent.sh → spare/N (free again)\n```\n\n**Windows are never auto-killed.** The worktree stays on its branch, the session stays alive. The agent is done working but the window, git state, and Claude session are all preserved until you choose to recycle.\n\n**To resume a done or crashed session:**\n```bash\n# Resume by stored session ID (preferred — exact session, full context)\nclaude --resume SESSION_ID --permission-mode bypassPermissions\n\n# Or resume most recent session in that worktree directory\ncd /path/to/worktree && claude --continue --permission-mode bypassPermissions\n```\n\n**To manually recycle when ready:**\n```bash\nbash ~/.claude/orchestrator/scripts/recycle-agent.sh SESSION:WIN WORKTREE_PATH spare/N\n# Then update state:\njq --arg w \"SESSION:WIN\" '.agents |= map(if .window == $w then .state = \"recycled\" else . end)' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## State file (`~/.claude/orchestrator-state.json`)\n\nNever committed to git. You maintain this file directly using `jq` + atomic writes (`.tmp` → `mv`).\n\n```json\n{\n  \"active\": true,\n  \"tmux_session\": \"autogpt1\",\n  \"idle_threshold_seconds\": 300,\n  \"loop_window\": \"autogpt1:5\",\n  \"repo\": \"Significant-Gravitas/AutoGPT\",\n  \"discord_webhook\": \"https://discord.com/api/webhooks/...\",\n  \"last_poll_at\": 0,\n  \"agents\": [\n    {\n      \"window\": \"autogpt1:3\",\n      \"worktree\": \"AutoGPT6\",\n      \"worktree_path\": \"/path/to/AutoGPT6\",\n      \"spare_branch\": \"spare/6\",\n      \"branch\": \"feat/my-feature\",\n      \"objective\": \"Implement X and open a PR\",\n      \"pr_number\": \"12345\",\n      \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"steps\": [\"pr-address\", \"pr-test\"],\n      \"checkpoints\": [\"pr-address\"],\n      \"state\": \"running\",\n      \"last_output_hash\": \"\",\n      \"last_seen_at\": 0,\n      \"spawned_at\": 0,\n      \"idle_since\": 0,\n      \"revision_count\": 0,\n      \"last_rebriefed_at\": 0\n    }\n  ]\n}\n```\n\nTop-level optional fields:\n- `repo` — GitHub `owner/repo` for CI/thread checks. Auto-derived from git remote if omitted.\n- `discord_webhook` — Discord webhook URL for completion notifications. Also reads `DISCORD_WEBHOOK_URL` env var.\n\nPer-agent fields:\n- `session_id` — UUID passed to `claude --session-id` at spawn; use with `claude --resume UUID` to restore exact session context after a crash or window close.\n- `last_rebriefed_at` — Unix timestamp of last re-brief; enforces 5-min cooldown to prevent spam.\n\nAgent states: `running` | `idle` | `stuck` | `waiting_approval` | `complete` | `done` | `escalated`\n\n`done` means verified complete — window is still open, session still alive, worktree still on task branch. Not recycled yet.\n\n## Serial /pr-test rule\n\n`/pr-test` and `/pr-test --fix` run local Docker + integration tests that use shared ports, a shared database, and shared build caches. **Running two `/pr-test` jobs simultaneously will cause port conflicts and database corruption.**\n\n**Rule: only one `/pr-test` runs at a time. The orchestrator serializes them.**\n\nYou (the orchestrating Claude) own the test queue:\n1. Agents do `pr-review` and `pr-address` in parallel — that's safe (they only push code and reply to GitHub).\n2. When a PR needs local testing, add it to your mental queue — don't give agents a `pr-test` step.\n3. Run `/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER --fix` yourself, sequentially.\n4. Feed results back to the relevant agent via `tmux send-keys`:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"Local tests for PR #N: <paste failure output or 'all passed'>. Fix any failures and push, then output ORCHESTRATOR:DONE.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n5. Wait for CI to confirm green before marking the agent done.\n\nIf multiple PRs need testing at the same time, pick the one furthest along (fewest pending CI checks) and test it first. Only start the next test after the previous one completes.\n\n## Session restore (tested and confirmed)\n\nAgent sessions are saved to disk. To restore a closed or crashed session:\n\n```bash\n# If session_id is in state (preferred):\nNEW_WIN=$(tmux new-window -t SESSION -n WORKTREE_NAME -P -F '#{window_index}')\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --resume SESSION_ID --permission-mode bypassPermissions\" Enter\n\n# If no session_id (use --continue for most recent session in that directory):\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --continue --permission-mode bypassPermissions\" Enter\n```\n\n`--continue` restores the full conversation history including all tool calls, file edits, and context. The agent resumes exactly where it left off. After restoring, update the window address in the state file:\n\n```bash\njq --arg old \"SESSION:OLD_WIN\" --arg new \"SESSION:NEW_WIN\" \\\n  '(.agents[] | select(.window == $old)).window = $new' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## Intent → action mapping\n\nMatch the user's message to one of these intents:\n\n| The user says something like… | What to do |\n|---|---|\n| \"status\", \"what's running\", \"show agents\" | Run `status.sh` + `capacity.sh`, show output |\n| \"how many free\", \"capacity\", \"available worktrees\" | Run `capacity.sh`, show output |\n| \"start N agents on X, Y, Z\" or \"run these tasks: …\" | See **Spawning agents** below |\n| \"add task: …\", \"add one more agent for …\" | See **Adding an agent** below |\n| \"stop\", \"shut down\", \"pause the fleet\" | See **Stopping** below |\n| \"poll\", \"check now\", \"run a cycle\" | Run `poll-cycle.sh`, process actions |\n| \"recycle window X\", \"free up autogpt3\" | Run `recycle-agent.sh` directly |\n\nWhen the intent is ambiguous, show capacity first and ask what tasks to run.\n\n## Spawning agents\n\n### 1. Resolve tmux session\n\n```bash\ntmux list-sessions -F \"#{session_name}: #{session_windows} windows\" 2>/dev/null\n```\n\nUse an existing session. **Never create a tmux session from within Claude** — it becomes a child of Claude's process and dies when the session ends. If no session exists, tell the user to run `tmux new-session -d -s autogpt1` in their terminal first, then re-invoke `/orchestrate`.\n\n### 2. Show available capacity\n\n```bash\nbash $SKILLS_DIR/capacity.sh $(git rev-parse --show-toplevel)\n```\n\n### 3. Collect tasks from the user\n\nFor each task, gather:\n- **objective** — what to do (e.g. \"implement feature X and open a PR\")\n- **branch name** — e.g. `feat/my-feature` (derive from objective if not given)\n- **pr_number** — GitHub PR number if working on an existing PR (for verification)\n- **steps** — required checkpoint names in order (e.g. `pr-address pr-test`) — derive from objective\n\nAsk for `idle_threshold_seconds` only if the user mentions it (default: 300).\n\nNever ask the user to specify a worktree — auto-assign from `find-spare.sh`.\n\n### 4. Spawn one agent per task\n\n```bash\n# Get ordered list of spare worktrees\nSPARE_LIST=$(bash $SKILLS_DIR/find-spare.sh $(git rev-parse --show-toplevel))\n\n# For each task, take the next spare line:\nWORKTREE_PATH=$(echo \"$SPARE_LINE\" | awk '{print $1}')\nSPARE_BRANCH=$(echo \"$SPARE_LINE\" | awk '{print $2}')\n\n# With PR number and required steps:\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\" \"$PR_NUMBER\" \"pr-address\" \"pr-test\")\n\n# Without PR (new work):\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\")\n```\n\nBuild an agent record and append it to the state file. If the state file doesn't exist yet, initialize it:\n\n```bash\n# Derive repo from git remote (used by verify-complete.sh + supervisor)\nREPO=$(git remote get-url origin 2>/dev/null | sed 's|.*github\\.com[:/]||; s|\\.git$||' || echo \"\")\n\njq -n \\\n  --arg session \"$SESSION\" \\\n  --arg repo \"$REPO\" \\\n  --argjson threshold 300 \\\n  '{active:true, tmux_session:$session, idle_threshold_seconds:$threshold,\n    repo:$repo, loop_window:null, supervisor_window:null, last_poll_at:0, agents:[]}' \\\n  > ~/.claude/orchestrator-state.json\n```\n\nOptionally add a Discord webhook for completion notifications:\n```bash\njq --arg hook \"$DISCORD_WEBHOOK_URL\" '.discord_webhook = $hook' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n`spawn-agent.sh` writes the initial agent record (window, worktree_path, branch, objective, state, etc.) to the state file automatically — **do not append the record again after calling it.** The record already exists and `pr_number`/`steps` are patched in by the script itself.\n\n### 5. Start the mechanical babysitter\n\n```bash\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\n\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n### 6. Begin supervising directly in this conversation\n\nYou are the supervisor. After spawning, immediately start your first poll loop (see **Supervisor duties** below) and continue every 2-3 minutes. Do NOT spawn a separate supervisor Claude window.\n\n## Adding an agent\n\nFind the next spare worktree, then spawn and append to state — same as steps 2–4 above but for a single task. If no spare worktrees are available, tell the user.\n\n## Supervisor duties (YOUR job, every 2-3 min in this conversation)\n\nYou are the supervisor. Run this poll loop directly in your Claude session — not in a separate window.\n\n### Poll loop mechanism\n\nYou are reactive — you only act when a tool completes or the user sends a message. To create a self-sustaining poll loop without user involvement:\n\n1. Start each poll with `run_in_background: true` + a sleep before the work:\n   ```bash\n   sleep 120 && tmux capture-pane -t autogpt1:0 -p -S -200 | tail -40\n   # + similar for each active window\n   ```\n2. When the background job notifies you, read the pane output and take action.\n3. Immediately schedule the next background poll — this keeps the loop alive.\n4. Stop scheduling when all agents are done/escalated.\n\n**Never tell the user \"I'll poll every 2-3 minutes\"** — that does nothing without a trigger. Start the background job instead.\n\n### Each poll: what to check\n\n```bash\n# 1. Read state\ncat ~/.claude/orchestrator-state.json | jq '.agents[] | {window, worktree, branch, state, pr_number, checkpoints}'\n\n# 2. For each running/stuck/idle agent, capture pane\ntmux capture-pane -t SESSION:WIN -p -S -200 | tail -60\n```\n\nFor each agent, decide:\n\n| What you see | Action |\n|---|---|\n| Spinner / tools running | Do nothing — agent is working |\n| Idle `❯` prompt, no `ORCHESTRATOR:DONE` | Stalled — send specific nudge with objective from state |\n| Stuck in error loop | Send targeted fix with exact error + solution |\n| Waiting for input / question | Answer and unblock via `tmux send-keys` |\n| CI red | `gh pr checks PR_NUMBER --repo REPO` → tell agent exactly what's failing |\n| GitHub abuse rate limit error | Nudge: \"Wait 60 seconds then continue posting replies with sleep 3 between each\" |\n| Context compacted / agent lost | Send recovery: `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"WIN\")'` + `gh pr view PR_NUMBER --json title,body` |\n| `ORCHESTRATOR:DONE` in output | Query GraphQL for actual unresolved count. If >0, re-brief. If 0, run `verify-complete.sh` |\n\n**Poll all windows from state, not from memory.** Before each poll, run:\n```bash\njq -r '.agents[] | select(.state | test(\"running|idle|stuck|waiting_approval|pending_evaluation\")) | .window' ~/.claude/orchestrator-state.json\n```\nand capture every window listed. If you manually added a window outside spawn-agent.sh, ensure it's in the state file first.\n\n### RUNNING count includes waiting_approval agents\n\nThe `RUNNING` count from run-loop.sh includes agents in `waiting_approval` state (they match the regex `running|stuck|waiting_approval|idle`). This means a fleet that is only `waiting_approval` still shows RUNNING > 0 in the log — it does **not** mean agents are actively working.\n\nWhen you see `RUNNING > 0` in the run-loop log but suspect agents are actually blocked, check state directly:\n```bash\njq '.agents[] | {window, state, worktree}' ~/.claude/orchestrator-state.json\n```\nA count of `running=1 waiting=1` in the log actually means one agent is waiting for approval — the orchestrator should check and approve, not wait.\n\n### State file staleness recovery\n\nThe state file is written by scripts but can drift from reality when windows are closed, sessions expire, or the orchestrator restarts across conversations.\n\n**Signs of stale state:**\n- `loop_window` points to a window that no longer exists in the tmux session\n- An agent's `state` is `running` but tmux window is closed or shows a shell prompt (not claude)\n- `last_seen_at` is hours old but state still says `running`\n\n**Recovery steps:**\n\n1. **Verify actual tmux windows:**\n```bash\ntmux list-windows -t SESSION -F '#{window_index}: #{window_name} (#{pane_current_command})'\n```\n\n2. **Cross-reference with state file:**\n```bash\njq -r '.agents[] | \"\\(.window) \\(.state) \\(.worktree)\"' ~/.claude/orchestrator-state.json\n```\n\n3. **Fix stale entries:**\n```bash\n# Agent window closed — mark idle so run-loop.sh will restart it\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window==$w)).state = \"idle\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\n# loop_window gone — kill the stale reference, then restart run-loop.sh\njq '.loop_window = null' ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n4. **After any state repair, re-run `status.sh` to confirm coherence before resuming supervision.**\n\n### Strict ORCHESTRATOR:DONE gate\n\n`verify-complete.sh` handles the main checks automatically (checkpoints, threads, CI green, spawned_at, and CHANGES_REQUESTED). Run it:\n\n**CHANGES_REQUESTED staleness rule**: a `CHANGES_REQUESTED` review only blocks if it was submitted *after* the latest commit. If the latest commit postdates the review, the review is considered stale (feedback already addressed) and does not block. This avoids false negatives when a bot reviewer hasn't re-reviewed after the agent's fixing commits.\n\n```bash\nSKILLS_DIR=~/.claude/orchestrator/scripts\nbash $SKILLS_DIR/verify-complete.sh SESSION:WIN\n```\n\nIf it passes → run-loop.sh will recycle the window automatically. No manual action needed.\nIf it fails → re-brief the agent with the failure reason. Never manually mark state `done` to bypass this.\n\n### Re-brief a stalled agent\n\n**Before sending any nudge, verify the pane is at an idle ❯ prompt.** Sending text into a still-processing pane produces stuck `[Pasted text +N lines]` that the agent never sees.\n\nCheck:\n```bash\ntmux capture-pane -t SESSION:WIN -p 2>/dev/null | tail -5\n```\nIf the last line shows a spinner (✳✽✢✶·), `Running…`, or no `❯` — wait 10–15s and check again before sending.\n\n```bash\nOBJ=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .objective' ~/.claude/orchestrator-state.json)\nPR=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .pr_number' ~/.claude/orchestrator-state.json)\ntmux send-keys -t SESSION:WIN \"You appear stalled. Your objective: $OBJ. Check: gh pr view $PR --json title,body,headRefName to reorient.\"\nsleep 0.3\ntmux send-keys -t SESSION:WIN Enter\n```\n\nIf `image_path` is set on the agent record, include: \"Re-read context at IMAGE_PATH with the Read tool.\"\n\n## Self-recovery protocol (agents)\n\nspawn-agent.sh automatically includes this instruction in every objective:\n\n> If your context compacts and you lose track of what to do, run:\n> `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"SESSION:WIN\")'`\n> and `gh pr view PR_NUMBER --json title,body,headRefName` to reorient.\n> Output each completed step as `CHECKPOINT:<step-name>` on its own line.\n\n## Passing images and screenshots to agents\n\n`tmux send-keys` is text-only — you cannot paste a raw image into a pane. To give an agent visual context (screenshots, diagrams, mockups):\n\n1. **Save the image to a temp file** with a stable path:\n   ```bash\n   # If the user drags in a screenshot or you receive a file path:\n   IMAGE_PATH=\"/tmp/orchestrator-context-$(date +%s).png\"\n   cp \"$USER_PROVIDED_PATH\" \"$IMAGE_PATH\"\n   ```\n\n2. **Reference the path in the objective string**:\n   ```bash\n   OBJECTIVE=\"Implement the layout shown in /tmp/orchestrator-context-1234567890.png. Read that image first with the Read tool to understand the design.\"\n   ```\n\n3. The agent uses its `Read` tool to view the image at startup — Claude Code agents are multimodal and can read image files directly.\n\n**Rule**: always use `/tmp/orchestrator-context-<timestamp>.png` as the naming convention so the supervisor knows what to look for if it needs to re-brief an agent with the same image.\n\n---\n\n## Orchestrator final evaluation (YOU decide, not the script)\n\n`verify-complete.sh` is a gate — it blocks premature marking. But it cannot tell you if the work is actually good. That is YOUR job.\n\nWhen run-loop marks an agent `pending_evaluation` and you're notified, do all of these before marking done:\n\n### 1. Run /pr-test (required, serialized, use TodoWrite to queue)\n\n`/pr-test` is the only reliable confirmation that the objective is actually met. Run it yourself, not the agent.\n\n**When multiple PRs reach `pending_evaluation` at the same time, use TodoWrite to queue them:**\n```\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/NNNN — <feature description>\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/MMMM — <feature description>\n```\nRun one at a time. Check off as you go.\n\n```\n/pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/PR_NUMBER\n```\n\n**/pr-test can be lazy** — if it gives vague output, re-run with full context:\n\n```\n/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER\nContext: This PR implements <objective from state file>. Key files: <list>.\nPlease verify: <specific behaviors to check>.\n```\n\nOnly one `/pr-test` at a time — they share ports and DB.\n\n### /pr-test result evaluation\n\n**PARTIAL on any headline feature scenario is an immediate blocker.** Do not approve, do not mark done, do not let the agent output `ORCHESTRATOR:DONE`.\n\n| `/pr-test` result | Action |\n|---|---|\n| All headline scenarios **PASS** | Proceed to evaluation step 2 |\n| Any headline scenario **PARTIAL** | Re-brief the agent immediately — see below |\n| Any headline scenario **FAIL** | Re-brief the agent immediately |\n\n**What PARTIAL means**: the feature is only partly working. Example: the Apply button never appeared, or the AI returned no action blocks. The agent addressed part of the objective but not all of it.\n\n**When any headline scenario is PARTIAL or FAIL:**\n\n1. Do NOT mark the agent done or accept `ORCHESTRATOR:DONE`\n2. Re-brief the agent with the specific scenario that failed and what was missing:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"PARTIAL result on /pr-test — S5 (Apply button) never appeared. The AI must output JSON action blocks for the Apply button to render. Fix this before re-running /pr-test.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n3. Set state back to `running`:\n   ```bash\n   jq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"running\"' \\\n     ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n   ```\n4. Wait for new `ORCHESTRATOR:DONE`, then re-run `/pr-test` from scratch\n\n**Rule: only ALL-PASS qualifies for approval.** A mix of PASS + PARTIAL is a failure.\n\n> **Why this matters**: A PR was once wrongly approved with S5 PARTIAL — the AI never output JSON action blocks so the Apply button never appeared. The fix was already in the agent's reach but slipped through because PARTIAL was not treated as blocking.\n\n### 2. Do your own evaluation\n\n1. **Read the PR diff and objective** — does the code actually implement what was asked? Is anything obviously missing or half-done?\n2. **Read the resolved threads** — were comments addressed with real fixes, or just dismissed/resolved without changes?\n3. **Check CI run names** — any suspicious retries that shouldn't have passed?\n4. **Check the PR description** — title, summary, test plan complete?\n\n### 3. Decide\n\n- `/pr-test` all scenarios PASS + evaluation looks good → mark `done` in state, tell the user the PR is ready, ask if window should be closed\n- `/pr-test` any scenario PARTIAL or FAIL → re-brief the agent with the specific failing scenario, set state back to `running` (see `/pr-test result evaluation` above)\n- Evaluation finds gaps even with all PASS → re-brief the agent with specific gaps, set state back to `running`\n\n**Never mark done based purely on script output.** You hold the full objective context; the script does not.\n\n```bash\n# Mark done after your positive evaluation:\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"done\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## When to stop the fleet\n\nStop the fleet (`active = false`) when **all** of the following are true:\n\n| Check | How to verify |\n|---|---|\n| All agents are `done` or `escalated` | `jq '[.agents[] | select(.state | test(\"running\\|stuck\\|idle\\|waiting_approval\"))] | length' ~/.claude/orchestrator-state.json` == 0 |\n| All PRs have 0 unresolved review threads | GraphQL `isResolved` check per PR |\n| All PRs have green CI **on a run triggered after the agent's last push** | `gh run list --branch BRANCH --limit 1` timestamp > `spawned_at` in state |\n| No fresh CHANGES_REQUESTED (after latest commit) | `verify-complete.sh` checks this — stale pre-commit reviews are ignored |\n| No agents are `escalated` without human review | If any are escalated, surface to user first |\n\n**Do NOT stop just because agents output `ORCHESTRATOR:DONE`.** That is a signal to verify, not a signal to stop.\n\n**Do stop** if the user explicitly says \"stop\", \"shut down\", or \"kill everything\", even with agents still running.\n\n```bash\n# Graceful stop\njq '.active = false' ~/.claude/orchestrator-state.json > /tmp/orch.tmp \\\n  && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\nLOOP_WINDOW=$(jq -r '.loop_window // \"\"' ~/.claude/orchestrator-state.json)\n[ -n \"$LOOP_WINDOW\" ] && tmux kill-window -t \"$LOOP_WINDOW\" 2>/dev/null || true\n```\n\nDoes **not** recycle running worktrees — agents may still be mid-task. Run `capacity.sh` to see what's still in progress.\n\n## tmux send-keys pattern\n\n**Always split long messages into text + Enter as two separate calls with a sleep between them.** If sent as one call (`\"text\" Enter`), Enter can fire before the full string is buffered into Claude's input — leaving the message stuck as `[Pasted text +N lines]` unsent.\n\n```bash\n# CORRECT — text then Enter separately\ntmux send-keys -t \"$WINDOW\" \"your long message here\"\nsleep 0.3\ntmux send-keys -t \"$WINDOW\" Enter\n\n# WRONG — Enter may fire before text is buffered\ntmux send-keys -t \"$WINDOW\" \"your long message here\" Enter\n```\n\nShort single-character sends (`y`, `Down`, empty Enter for dialog approval) are safe to combine since they have no buffering lag.\n\n---\n\n## Protected worktrees\n\nSome worktrees must **never** be used as spare worktrees for agent tasks because they host files critical to the orchestrator itself:\n\n| Worktree | Protected branch | Why |\n|---|---|---|\n| `AutoGPT1` | `dx/orchestrate-skill` | Hosts the orchestrate skill scripts. `recycle-agent.sh` would check out `spare/1`, wiping `.claude/skills/` and breaking all subsequent `spawn-agent.sh` calls. |\n\n**Rule**: when selecting spare worktrees via `find-spare.sh`, skip any worktree whose CURRENT branch matches a protected branch. If you accidentally spawn an agent in a protected worktree, do not let `recycle-agent.sh` run on it — manually restore the branch after the agent finishes.\n\nWhen `dx/orchestrate-skill` is merged into `dev`, `AutoGPT1` becomes a normal spare again.\n\n---\n\n## Thread resolution integrity (critical)\n\n**Agents MUST NOT resolve review threads via GraphQL unless a real code fix has been committed and pushed first.**\n\nThis is the most common failure mode: agents call `resolveReviewThread` to make unresolved counts drop without actually fixing anything. This produces a false \"done\" signal that gets past verify-complete.sh.\n\n**The only valid resolution sequence:**\n1. Read the thread and understand what it's asking\n2. Make the actual code change\n3. `git commit` and `git push`\n4. Reply to the thread with the commit SHA (e.g. \"Fixed in `abc1234`\")\n5. THEN call `resolveReviewThread`\n\n**The supervisor must verify actual thread counts via GraphQL** — never trust an agent's claim of \"0 unresolved.\" After any agent's ORCHESTRATOR:DONE, always run:\n\n```bash\n# Step 1: get total count\nTOTAL=$(gh api graphql -f query='{ repository(owner: \"OWNER\", name: \"REPO\") { pullRequest(number: PR) { reviewThreads { totalCount } } } }' \\\n  | jq '.data.repository.pullRequest.reviewThreads.totalCount')\necho \"Total threads: $TOTAL\"\n\n# Step 2: paginate all pages and count unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"{ repository(owner: \\\"OWNER\\\", name: \\\"REPO\\\") { pullRequest(number: PR) { reviewThreads(first: 100${AFTER}) { pageInfo { hasNextPage endCursor } nodes { isResolved } } } } }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved: $UNRESOLVED\"\n```\n\nIf unresolved > 0, the agent is NOT done — re-brief with the actual count and the rule.\n\n**Include this in every agent objective:**\n> IMPORTANT: Do NOT resolve any review thread via GraphQL unless the code fix is committed and pushed first. Fix the code → commit → push → reply with SHA → then resolve. Never resolve without a real commit. \"Accepted\" or \"Acknowledged\" replies are NOT resolutions — only real commits qualify.\n\n### Detecting fake resolutions\n\nWhen an agent claims \"0 unresolved threads\", query GitHub GraphQL yourself and also inspect how each thread was resolved. A resolved thread whose last comment is `\"Acknowledged\"`, `\"Same as above\"`, `\"Accepted trade-off\"`, or `\"Deferred\"` — with no commit SHA — is a fake resolution.\n\nTo spot these, paginate all pages and collect resolved threads with missing SHA links:\n```bash\n# Paginate all pages — first:100 misses threads beyond page 1 on large PRs\nCURSOR=\"\"; FAKE_RESOLUTIONS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: PR_NUMBER) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved\n            comments(last: 1) {\n              nodes { body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  PAGE_FAKES=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[]\n    | select(.isResolved == true)\n    | {body: .comments.nodes[0].body[:120], author: .comments.nodes[0].author.login}\n    | select(.body | test(\"Fixed in|Removed in|Addressed in\") | not)]')\n  FAKE_RESOLUTIONS=$(echo \"$FAKE_RESOLUTIONS $PAGE_FAKES\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"$FAKE_RESOLUTIONS\"\n```\nAny resolved thread whose last comment does NOT contain `\"Fixed in\"`, `\"Removed in\"`, or `\"Addressed in\"` (with a commit link) should be investigated — either the agent falsely resolved it, or it was a genuine false positive that needs explanation.\n\n## GitHub abuse rate limits\n\nTwo distinct rate limits exist with different recovery times:\n\n| Error | HTTP status | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` in body | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `API rate limit exceeded` | 429 | Primary rate limit — too many read calls per hour | Wait until `X-RateLimit-Reset` timestamp |\n\n**Prevention:** Agents must add `sleep 3` between individual thread reply API calls. For >20 unresolved threads, increase to `sleep 5`.\n\nIf you see a 403 `abuse` error from an agent's pane:\n1. Nudge the agent: `\"You hit a GitHub secondary rate limit (403). Stop all API writes. Wait 2 minutes, then resume with sleep 3 between each thread reply.\"`\n2. Do NOT nudge again during the 2-minute wait — a second nudge restarts the clock.\n\nAdd this to agent briefings when there are >20 unresolved threads:\n> Post replies with `sleep 3` between each reply. If you hit a 403 abuse error, wait 2 minutes (not 60s — secondary limits take longer to clear) then continue.\n\n## Key rules\n\n1. **Scripts do all the heavy lifting** — don't reimplement their logic inline in this file\n2. **Never ask the user to pick a worktree** — auto-assign from `find-spare.sh` output\n3. **Never restart a running agent** — only restart on `idle` kicks (foreground is a shell)\n4. **Auto-dismiss settings dialogs** — if \"Enter to confirm\" appears, send Down+Enter\n5. **Always `--permission-mode bypassPermissions`** on every spawn\n6. **Escalate after 3 kicks** — mark `escalated`, surface to user\n7. **Atomic state writes** — always write to `.tmp` then `mv`\n8. **Never approve destructive commands** outside the worktree scope — when in doubt, escalate\n9. **Never recycle without verification** — `verify-complete.sh` must pass before recycling\n10. **No TASK.md files** — commit risk; use state file + `gh pr view` for agent context persistence\n11. **Re-brief stalled agents** — read objective from state file + `gh pr view`, send via tmux\n12. **ORCHESTRATOR:DONE is a signal to verify, not to accept** — always run `verify-complete.sh` and check CI run timestamp before recycling\n13. **Protected worktrees** — never use the worktree hosting the skill scripts as a spare\n14. **Images via file path** — save screenshots to `/tmp/orchestrator-context-<ts>.png`, pass path in objective; agents read with the `Read` tool\n15. **Split send-keys** — always separate text and Enter with `sleep 0.3` between calls for long strings\n16. **Poll ALL windows from state file** — never hardcode window count. Derive active windows dynamically: `jq -r '.agents[] | select(.state | test(\"running|idle|stuck\")) | .window' ~/.claude/orchestrator-state.json`. If you added a window mid-session outside spawn-agent.sh, add it to the state file immediately.\n20. **Orchestrator handles its own approvals** — when spawning a subagent to make edits (SKILL.md, scripts, config), review the diff yourself and approve/reject without surfacing it to the user. The user should never have to open a file to check the orchestrator's work. Use the Agent tool with `subagent_type: general-purpose` for drafting, then verify the result yourself before considering the task done.\n17. **Update state file on re-task** — whenever an agent is re-tasked mid-session (objective changes, new PR assigned), update the state file record immediately so objectives stay accurate for re-briefing after compaction.\n18. **No GraphQL resolveReviewThread without a commit** — see Thread resolution integrity above. This is rule #1 for pr-address work.\n19. **Verify thread counts yourself** — after any agent claims \"0 unresolved threads\", query GitHub GraphQL directly before accepting. Never trust the agent's self-report.\n",".claude/skills/worktree/SKILL.md":"---\nname: worktree\ndescription: Set up a new git worktree for parallel development. Creates the worktree, copies .env files, installs dependencies, and generates Prisma client. TRIGGER when user asks to set up a worktree, work on a branch in isolation, or needs a separate environment for a branch or PR.\nuser-invocable: true\nargs: \"[name] — optional worktree name (e.g., 'AutoGPT7'). If omitted, uses next available AutoGPT<N>.\"\nmetadata:\n  author: autogpt-team\n  version: \"3.0.0\"\n---\n\n# Worktree Setup\n\n## Create the worktree\n\nDerive paths from the git toplevel. If a name is provided as argument, use it. Otherwise, check `git worktree list` and pick the next `AutoGPT<N>`.\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nPARENT=$(dirname \"$ROOT\")\n\n# From an existing branch\ngit worktree add \"$PARENT/<NAME>\" <branch-name>\n\n# From a new branch off dev\ngit worktree add -b <new-branch> \"$PARENT/<NAME>\" dev\n```\n\n## Copy environment files\n\nCopy `.env` from the root worktree. Falls back to `.env.default` if `.env` doesn't exist.\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nTARGET=\"$(dirname \"$ROOT\")/<NAME>\"\n\nfor envpath in autogpt_platform/backend autogpt_platform/frontend autogpt_platform; do\n  if [ -f \"$ROOT/$envpath/.env\" ]; then\n    cp \"$ROOT/$envpath/.env\" \"$TARGET/$envpath/.env\"\n  elif [ -f \"$ROOT/$envpath/.env.default\" ]; then\n    cp \"$ROOT/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n  fi\ndone\n```\n\n## Install dependencies\n\n```bash\nTARGET=\"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\ncd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install\ncd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate\ncd \"$TARGET/autogpt_platform/frontend\" && pnpm install\n```\n\nReplace `<NAME>` with the actual worktree name (e.g., `AutoGPT7`).\n\n## Running the app (optional)\n\nBackend uses ports: 8001, 8002, 8003, 8005, 8006, 8007, 8008. Free them first if needed:\n\n```bash\nTARGET=\"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\nfor port in 8001 8002 8003 8005 8006 8007 8008; do\n  lsof -ti :$port | xargs kill -9 2>/dev/null || true\ndone\ncd \"$TARGET/autogpt_platform/backend\" && poetry run app\n```\n\n## CoPilot testing\n\nSDK mode spawns a Claude subprocess — won't work inside Claude Code. Set `CHAT_USE_CLAUDE_AGENT_SDK=false` in `backend/.env` to use baseline mode.\n\n## Cleanup\n\n```bash\n# Replace <NAME> with the actual worktree name (e.g., AutoGPT7)\ngit worktree remove \"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\n```\n\n## Alternative: Branchlet (optional)\n\nIf [branchlet](https://www.npmjs.com/package/branchlet) is installed:\n\n```bash\nbranchlet create -n <name> -s <source-branch> -b <new-branch>\n```\n","autogpt_platform/backend/test/fixtures/skills/webapp-testing/SKILL.md":"---\nname: webapp-testing\ndescription: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.\nlicense: Complete terms in LICENSE.txt\n---\n\n# Web Application Testing\n\nTo test local web applications, write native Python Playwright scripts.\n\n**Helper Scripts Available**:\n- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)\n\n**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.\n\n## Decision Tree: Choosing Your Approach\n\n```\nUser task → Is it static HTML?\n    ├─ Yes → Read HTML file directly to identify selectors\n    │         ├─ Success → Write Playwright script using selectors\n    │         └─ Fails/Incomplete → Treat as dynamic (below)\n    │\n    └─ No (dynamic webapp) → Is the server already running?\n        ├─ No → Run: python scripts/with_server.py --help\n        │        Then use the helper + write simplified Playwright script\n        │\n        └─ Yes → Reconnaissance-then-action:\n            1. Navigate and wait for networkidle\n            2. Take screenshot or inspect DOM\n            3. Identify selectors from rendered state\n            4. Execute actions with discovered selectors\n```\n\n## Example: Using with_server.py\n\nTo start a server, run `--help` first, then use the helper:\n\n**Single server:**\n```bash\npython scripts/with_server.py --server \"npm run dev\" --port 5173 -- python your_automation.py\n```\n\n**Multiple servers (e.g., backend + frontend):**\n```bash\npython scripts/with_server.py \\\n  --server \"cd backend && python server.py\" --port 3000 \\\n  --server \"cd frontend && npm run dev\" --port 5173 \\\n  -- python your_automation.py\n```\n\nTo create an automation script, include only Playwright logic (servers are managed automatically):\n```python\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode\n    page = browser.new_page()\n    page.goto('http://localhost:5173') # Server already running and ready\n    page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute\n    # ... your automation logic\n    browser.close()\n```\n\n## Reconnaissance-Then-Action Pattern\n\n1. **Inspect rendered DOM**:\n   ```python\n   page.screenshot(path='/tmp/inspect.png', full_page=True)\n   content = page.content()\n   page.locator('button').all()\n   ```\n\n2. **Identify selectors** from inspection results\n\n3. **Execute actions** using discovered selectors\n\n## Common Pitfall\n\n❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps\n✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection\n\n## Best Practices\n\n- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. \n- Use `sync_playwright()` for synchronous scripts\n- Always close the browser when done\n- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs\n- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`\n\n## Reference Files\n\n- **examples/** - Examples showing common patterns:\n  - `element_discovery.py` - Discovering buttons, links, and inputs on a page\n  - `static_html_automation.py` - Using file:// URLs for local HTML\n  - `console_logging.py` - Capturing console logs during automation",".claude/skills/setup-repo/SKILL.md":"---\nname: setup-repo\ndescription: Initialize a worktree-based repo layout for parallel development. Creates a main worktree, a reviews worktree for PR reviews, and N numbered work branches. Handles .env creation, dependency installation, and branchlet config. TRIGGER when user asks to set up the repo from scratch, initialize worktrees, bootstrap their dev environment, \"setup repo\", \"setup worktrees\", \"initialize dev environment\", \"set up branches\", or when a freshly cloned repo has no sibling worktrees.\nuser-invocable: true\nargs: \"No arguments — interactive setup via prompts.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Repository Setup\n\nThis skill sets up a worktree-based development layout from a freshly cloned repo. It creates:\n- A **main** worktree (the primary checkout)\n- A **reviews** worktree (for PR reviews)\n- **N work branches** (branch1..branchN) for parallel development\n\n## Step 1: Identify the repo\n\nDetermine the repo root and parent directory:\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nREPO_NAME=$(basename \"$ROOT\")\nPARENT=$(dirname \"$ROOT\")\n```\n\nDetect if the repo is already inside a worktree layout by counting sibling worktrees (not just checking the directory name, which could be anything):\n\n```bash\n# Count worktrees that are siblings (live under $PARENT but aren't $ROOT itself)\nSIBLING_COUNT=$(git worktree list --porcelain 2>/dev/null | grep \"^worktree \" | grep -c \"$PARENT/\" || true)\nif [ \"$SIBLING_COUNT\" -gt 1 ]; then\n  echo \"INFO: Existing worktree layout detected at $PARENT ($SIBLING_COUNT worktrees)\"\n  # Use $ROOT as-is; skip renaming/restructuring\nelse\n  echo \"INFO: Fresh clone detected, proceeding with setup\"\nfi\n```\n\n## Step 2: Ask the user questions\n\nUse AskUserQuestion to gather setup preferences:\n\n1. **How many parallel work branches do you need?** (Options: 4, 8, 16, or custom)\n   - These become `branch1` through `branchN`\n2. **Which branch should be the base?** (Options: origin/master, origin/dev, or custom)\n   - All work branches and reviews will start from this\n\n## Step 3: Fetch and set up branches\n\n```bash\ncd \"$ROOT\"\ngit fetch origin\n\n# Create the reviews branch from base (skip if already exists)\nif git show-ref --verify --quiet refs/heads/reviews; then\n  echo \"INFO: Branch 'reviews' already exists, skipping\"\nelse\n  git branch reviews <base-branch>\nfi\n\n# Create numbered work branches from base (skip if already exists)\nfor i in $(seq 1 \"$COUNT\"); do\n  if git show-ref --verify --quiet \"refs/heads/branch$i\"; then\n    echo \"INFO: Branch 'branch$i' already exists, skipping\"\n  else\n    git branch \"branch$i\" <base-branch>\n  fi\ndone\n```\n\n## Step 4: Create worktrees\n\nCreate worktrees as siblings to the main checkout:\n\n```bash\nif [ -d \"$PARENT/reviews\" ]; then\n  echo \"INFO: Worktree '$PARENT/reviews' already exists, skipping\"\nelse\n  git worktree add \"$PARENT/reviews\" reviews\nfi\n\nfor i in $(seq 1 \"$COUNT\"); do\n  if [ -d \"$PARENT/branch$i\" ]; then\n    echo \"INFO: Worktree '$PARENT/branch$i' already exists, skipping\"\n  else\n    git worktree add \"$PARENT/branch$i\" \"branch$i\"\n  fi\ndone\n```\n\n## Step 5: Set up environment files\n\n**Do NOT assume .env files exist.** For each worktree (including main if needed):\n\n1. Check if `.env` exists in the source worktree for each path\n2. If `.env` exists, copy it\n3. If only `.env.default` or `.env.example` exists, copy that as `.env`\n4. If neither exists, warn the user and list which env files are missing\n\nEnv file locations to check (same as the `/worktree` skill — keep these in sync):\n- `autogpt_platform/.env`\n- `autogpt_platform/backend/.env`\n- `autogpt_platform/frontend/.env`\n\n> **Note:** This env copying logic intentionally mirrors the `/worktree` skill's approach. If you update the path list or fallback logic here, update `/worktree` as well.\n\n```bash\nSOURCE=\"$ROOT\"\nWORKTREES=\"reviews\"\nfor i in $(seq 1 \"$COUNT\"); do WORKTREES=\"$WORKTREES branch$i\"; done\n\nFOUND_ANY_ENV=0\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  for envpath in autogpt_platform autogpt_platform/backend autogpt_platform/frontend; do\n    if [ -f \"$SOURCE/$envpath/.env\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env\" \"$TARGET/$envpath/.env\"\n    elif [ -f \"$SOURCE/$envpath/.env.default\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.default — you may need to edit it\"\n    elif [ -f \"$SOURCE/$envpath/.env.example\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.example\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.example — you may need to edit it\"\n    else\n      echo \"WARNING: No .env, .env.default, or .env.example found at $SOURCE/$envpath/\"\n    fi\n  done\ndone\n\nif [ \"$FOUND_ANY_ENV\" -eq 0 ]; then\n  echo \"WARNING: No environment files or templates were found in the source worktree.\"\n  # Use AskUserQuestion to confirm: \"Continue setup without env files?\"\n  # If the user declines, stop here and let them set up .env files first.\nfi\n```\n\n## Step 6: Copy branchlet config\n\nCopy `.branchlet.json` from main to each worktree so branchlet can manage sub-worktrees:\n\n```bash\nif [ -f \"$ROOT/.branchlet.json\" ]; then\n  for wt in $WORKTREES; do\n    cp \"$ROOT/.branchlet.json\" \"$PARENT/$wt/.branchlet.json\"\n  done\nfi\n```\n\n## Step 7: Install dependencies\n\nInstall deps in all worktrees. Run these sequentially per worktree:\n\n```bash\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  echo \"=== Installing deps for $wt ===\"\n  (cd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install) &&\n  (cd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate) &&\n  (cd \"$TARGET/autogpt_platform/frontend\" && pnpm install) &&\n  echo \"=== Done: $wt ===\" ||\n  echo \"=== FAILED: $wt ===\"\ndone\n```\n\nThis is slow. Run in background if possible and notify when complete.\n\n## Step 8: Verify and report\n\nAfter setup, verify and report to the user:\n\n```bash\ngit worktree list\n```\n\nSummarize:\n- Number of worktrees created\n- Which env files were copied vs created from defaults vs missing\n- Any warnings or errors encountered\n\n## Final directory layout\n\n```\nparent/\n  main/              # Primary checkout (already exists)\n  reviews/           # PR review worktree\n  branch1/           # Work branch 1\n  branch2/           # Work branch 2\n  ...\n  branchN/           # Work branch N\n```\n",".claude/skills/vercel-react-best-practices/SKILL.md":"---\nname: vercel-react-best-practices\ndescription: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.\nlicense: MIT\nmetadata:\n  author: vercel\n  version: \"1.0.0\"\n---\n\n# Vercel React Best Practices\n\nComprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 45 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.\n\n## When to Apply\n\nReference these guidelines when:\n- Writing new React components or Next.js pages\n- Implementing data fetching (client or server-side)\n- Reviewing code for performance issues\n- Refactoring existing React/Next.js code\n- Optimizing bundle size or load times\n\n## Rule Categories by Priority\n\n| Priority | Category | Impact | Prefix |\n|----------|----------|--------|--------|\n| 1 | Eliminating Waterfalls | CRITICAL | `async-` |\n| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |\n| 3 | Server-Side Performance | HIGH | `server-` |\n| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |\n| 5 | Re-render Optimization | MEDIUM | `rerender-` |\n| 6 | Rendering Performance | MEDIUM | `rendering-` |\n| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |\n| 8 | Advanced Patterns | LOW | `advanced-` |\n\n## Quick Reference\n\n### 1. Eliminating Waterfalls (CRITICAL)\n\n- `async-defer-await` - Move await into branches where actually used\n- `async-parallel` - Use Promise.all() for independent operations\n- `async-dependencies` - Use better-all for partial dependencies\n- `async-api-routes` - Start promises early, await late in API routes\n- `async-suspense-boundaries` - Use Suspense to stream content\n\n### 2. Bundle Size Optimization (CRITICAL)\n\n- `bundle-barrel-imports` - Import directly, avoid barrel files\n- `bundle-dynamic-imports` - Use next/dynamic for heavy components\n- `bundle-defer-third-party` - Load analytics/logging after hydration\n- `bundle-conditional` - Load modules only when feature is activated\n- `bundle-preload` - Preload on hover/focus for perceived speed\n\n### 3. Server-Side Performance (HIGH)\n\n- `server-cache-react` - Use React.cache() for per-request deduplication\n- `server-cache-lru` - Use LRU cache for cross-request caching\n- `server-serialization` - Minimize data passed to client components\n- `server-parallel-fetching` - Restructure components to parallelize fetches\n- `server-after-nonblocking` - Use after() for non-blocking operations\n\n### 4. Client-Side Data Fetching (MEDIUM-HIGH)\n\n- `client-swr-dedup` - Use SWR for automatic request deduplication\n- `client-event-listeners` - Deduplicate global event listeners\n\n### 5. Re-render Optimization (MEDIUM)\n\n- `rerender-defer-reads` - Don't subscribe to state only used in callbacks\n- `rerender-memo` - Extract expensive work into memoized components\n- `rerender-dependencies` - Use primitive dependencies in effects\n- `rerender-derived-state` - Subscribe to derived booleans, not raw values\n- `rerender-functional-setstate` - Use functional setState for stable callbacks\n- `rerender-lazy-state-init` - Pass function to useState for expensive values\n- `rerender-transitions` - Use startTransition for non-urgent updates\n\n### 6. Rendering Performance (MEDIUM)\n\n- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element\n- `rendering-content-visibility` - Use content-visibility for long lists\n- `rendering-hoist-jsx` - Extract static JSX outside components\n- `rendering-svg-precision` - Reduce SVG coordinate precision\n- `rendering-hydration-no-flicker` - Use inline script for client-only data\n- `rendering-activity` - Use Activity component for show/hide\n- `rendering-conditional-render` - Use ternary, not && for conditionals\n\n### 7. JavaScript Performance (LOW-MEDIUM)\n\n- `js-batch-dom-css` - Group CSS changes via classes or cssText\n- `js-index-maps` - Build Map for repeated lookups\n- `js-cache-property-access` - Cache object properties in loops\n- `js-cache-function-results` - Cache function results in module-level Map\n- `js-cache-storage` - Cache localStorage/sessionStorage reads\n- `js-combine-iterations` - Combine multiple filter/map into one loop\n- `js-length-check-first` - Check array length before expensive comparison\n- `js-early-exit` - Return early from functions\n- `js-hoist-regexp` - Hoist RegExp creation outside loops\n- `js-min-max-loop` - Use loop for min/max instead of sort\n- `js-set-map-lookups` - Use Set/Map for O(1) lookups\n- `js-tosorted-immutable` - Use toSorted() for immutability\n\n### 8. Advanced Patterns (LOW)\n\n- `advanced-event-handler-refs` - Store event handlers in refs\n- `advanced-use-latest` - useLatest for stable callback refs\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/async-parallel.md\nrules/bundle-barrel-imports.md\nrules/_sections.md\n```\n\nEach rule file contains:\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n\n## Full Compiled Document\n\nFor the complete guide with all rules expanded: `AGENTS.md`\n",".claude/skills/write-frontend-tests/SKILL.md":"---\nname: write-frontend-tests\ndescription: \"Analyze the current branch diff against dev, plan integration tests for changed frontend pages/components, and write them. TRIGGER when user asks to write frontend tests, add test coverage, or 'write tests for my changes'.\"\nuser-invocable: true\nargs: \"[base branch] — defaults to dev. Optionally pass a specific base branch to diff against.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Write Frontend Tests\n\nAnalyze the current branch's frontend changes, plan integration tests, and write them.\n\n## References\n\nBefore writing any tests, read the testing rules and conventions:\n\n- `autogpt_platform/frontend/TESTING.md` — testing strategy, file locations, examples\n- `autogpt_platform/frontend/src/tests/AGENTS.md` — detailed testing rules, MSW patterns, decision flowchart\n- `autogpt_platform/frontend/src/tests/integrations/test-utils.tsx` — custom render with providers\n- `autogpt_platform/frontend/src/tests/integrations/vitest.setup.tsx` — MSW server setup\n\n## Step 1: Identify changed frontend files\n\n```bash\nBASE_BRANCH=\"${ARGUMENTS:-dev}\"\ncd autogpt_platform/frontend\n\n# Get changed frontend files (excluding generated, config, and test files)\ngit diff \"$BASE_BRANCH\"...HEAD --name-only -- src/ \\\n  | grep -v '__generated__' \\\n  | grep -v '__tests__' \\\n  | grep -v '\\.test\\.' \\\n  | grep -v '\\.stories\\.' \\\n  | grep -v '\\.spec\\.'\n```\n\nAlso read the diff to understand what changed:\n\n```bash\ngit diff \"$BASE_BRANCH\"...HEAD --stat -- src/\ngit diff \"$BASE_BRANCH\"...HEAD -- src/ | head -500\n```\n\n## Step 2: Categorize changes and find test targets\n\nFor each changed file, determine:\n\n1. **Is it a page?** (`page.tsx`) — these are the primary test targets\n2. **Is it a hook?** (`use*.ts`) — test via the page/component that uses it; avoid direct `renderHook()` tests unless it is a shared reusable hook with standalone business logic\n3. **Is it a component?** (`.tsx` in `components/`) — test via the parent page unless it's complex enough to warrant isolation\n4. **Is it a helper?** (`helpers.ts`, `utils.ts`) — unit test directly if pure logic\n\n**Priority order:**\n\n1. Pages with new/changed data fetching or user interactions\n2. Components with complex internal logic (modals, forms, wizards)\n3. Shared hooks with standalone business logic when UI-level coverage is impractical\n4. Pure helper functions\n\nSkip: styling-only changes, type-only changes, config changes.\n\n## Step 3: Check for existing tests\n\nFor each test target, check if tests already exist:\n\n```bash\n# For a page at src/app/(platform)/library/page.tsx\nls src/app/\\(platform\\)/library/__tests__/ 2>/dev/null\n\n# For a component at src/app/(platform)/library/components/AgentCard/AgentCard.tsx\nls src/app/\\(platform\\)/library/components/AgentCard/__tests__/ 2>/dev/null\n```\n\nNote which targets have no tests (need new files) vs which have tests that need updating.\n\n## Step 4: Identify API endpoints used\n\nFor each test target, find which API hooks are used:\n\n```bash\n# Find generated API hook imports in the changed files\ngrep -rn 'from.*__generated__/endpoints' src/app/\\(platform\\)/library/\ngrep -rn 'use[A-Z].*V[12]' src/app/\\(platform\\)/library/\n```\n\nFor each API hook found, locate the corresponding MSW handler:\n\n```bash\n# If the page uses useGetV2ListLibraryAgents, find its MSW handlers\ngrep -rn 'getGetV2ListLibraryAgents.*Handler' src/app/api/__generated__/endpoints/library/library.msw.ts\n```\n\nList every MSW handler you will need (200 for happy path, 4xx for error paths).\n\n## Step 5: Write the test plan\n\nBefore writing code, output a plan as a numbered list:\n\n```\nTest plan for [branch name]:\n\n1. src/app/(platform)/library/__tests__/main.test.tsx (NEW)\n   - Renders page with agent list (MSW 200)\n   - Shows loading state\n   - Shows error state (MSW 422)\n   - Handles empty agent list\n\n2. src/app/(platform)/library/__tests__/search.test.tsx (NEW)\n   - Filters agents by search query\n   - Shows no results message\n   - Clears search\n\n3. src/app/(platform)/library/components/AgentCard/__tests__/AgentCard.test.tsx (UPDATE)\n   - Add test for new \"duplicate\" action\n```\n\nPresent this plan to the user. Wait for confirmation before proceeding. If the user has feedback, adjust the plan.\n\n## Step 6: Write the tests\n\nFor each test file in the plan, follow these conventions:\n\n### File structure\n\n```tsx\nimport { render, screen, waitFor } from \"@/tests/integrations/test-utils\";\nimport { server } from \"@/mocks/mock-server\";\n// Import MSW handlers for endpoints the page uses\nimport {\n  getGetV2ListLibraryAgentsMockHandler200,\n  getGetV2ListLibraryAgentsMockHandler422,\n} from \"@/app/api/__generated__/endpoints/library/library.msw\";\n// Import the component under test\nimport LibraryPage from \"../page\";\n\ndescribe(\"LibraryPage\", () => {\n  test(\"renders agent list from API\", async () => {\n    server.use(getGetV2ListLibraryAgentsMockHandler200());\n\n    render(<LibraryPage />);\n\n    expect(await screen.findByText(/all workflows/i)).toBeDefined();\n  });\n\n  test(\"shows error state on API failure\", async () => {\n    server.use(getGetV2ListLibraryAgentsMockHandler422());\n\n    render(<LibraryPage />);\n\n    expect(await screen.findByText(/error/i)).toBeDefined();\n  });\n});\n```\n\n### Rules\n\n- Use `render()` from `@/tests/integrations/test-utils` (NOT from `@testing-library/react` directly)\n- Use `server.use()` to set up MSW handlers BEFORE rendering\n- Use `findBy*` (async) for elements that appear after data fetching — NOT `getBy*`\n- Use `getBy*` only for elements that are immediately present in the DOM\n- Use `screen` queries — do NOT destructure from `render()`\n- Use `waitFor` when asserting side effects or state changes after interactions\n- Import `fireEvent` or `userEvent` from the test-utils for interactions\n- Do NOT mock internal hooks or functions — mock at the API boundary via MSW\n- Prefer Orval-generated MSW handlers and response builders over hand-built API response objects\n- Do NOT use `act()` manually — `render` and `fireEvent` handle it\n- Keep tests focused: one behavior per test\n- Use descriptive test names that read like sentences\n\n### Test location\n\n```\n# For pages: __tests__/ next to page.tsx\nsrc/app/(platform)/library/__tests__/main.test.tsx\n\n# For complex standalone components: __tests__/ inside component folder\nsrc/app/(platform)/library/components/AgentCard/__tests__/AgentCard.test.tsx\n\n# For pure helpers: co-located .test.ts\nsrc/app/(platform)/library/helpers.test.ts\n```\n\n### Custom MSW overrides\n\nWhen the auto-generated faker data is not enough, override with specific data:\n\n```tsx\nimport { http, HttpResponse } from \"msw\";\n\nserver.use(\n  http.get(\"http://localhost:3000/api/proxy/api/v2/library/agents\", () => {\n    return HttpResponse.json({\n      agents: [{ id: \"1\", name: \"Test Agent\", description: \"A test agent\" }],\n      pagination: { total_items: 1, total_pages: 1, page: 1, page_size: 10 },\n    });\n  }),\n);\n```\n\nUse the proxy URL pattern: `http://localhost:3000/api/proxy/api/v{version}/{path}` — this matches the MSW base URL configured in `orval.config.ts`.\n\n## Step 7: Run and verify\n\nAfter writing all tests:\n\n```bash\ncd autogpt_platform/frontend\npnpm test:unit --reporter=verbose\n```\n\nIf tests fail:\n\n1. Read the error output carefully\n2. Fix the test (not the source code, unless there is a genuine bug)\n3. Re-run until all pass\n\nThen run the full checks:\n\n```bash\npnpm format\npnpm lint\npnpm types\n```\n",".claude/skills/pr-test/SKILL.md":"---\nname: pr-test\ndescription: \"E2E manual testing of PRs/branches using docker compose, agent-browser, and API calls. TRIGGER when user asks to manually test a PR, test a feature end-to-end, or run integration tests against a running system.\"\nuser-invocable: true\nargument-hint: \"[worktree path or PR number] — tests the PR in the given worktree. Optional flags: --fix (auto-fix issues found)\"\nmetadata:\n  author: autogpt-team\n  version: \"2.3.0\"\n---\n\n# Manual E2E Test\n\nTest a PR/branch end-to-end by building the full platform, interacting via browser and API, capturing screenshots, and reporting results.\n\n**Changelog 2.2.0** — auth flow updated for Better Auth (Supabase signup is\ngone), env-setup gaps closed, a proven Playwright fallback for agent-browser,\na billing-test trap that produces false passes, safer process cleanup, and a\nmock-provider pattern for deterministic $0 testing. Learned on\n[#14206](https://github.com/Significant-Gravitas/AutoGPT/pull/14206) — see the\n[evidence comment](https://github.com/Significant-Gravitas/AutoGPT/pull/14206#issuecomment-5511429524).\n**2.2.1** — corrects two claims from 2.2.0 that didn't survive live-stack\nverification (JWKS does **not** rotate on a frontend restart; the local\nPostgres port is `5432`, not `54322`) and hardens the auth setup (explicit\npassword-length/allowlist/rate-limit failure modes, fail-fast on an empty\ntoken, password kept out of process args).\n**2.3.0** — screenshots are posted as GitHub comment attachments\n(`gh pr comment --attach`, needs gh >= 2.99.0) instead of being pushed to a\n`test-screenshots/*` branch, which accumulated one branch per tested PR and\nmade every old report's images depend on that branch surviving.\n\n## Critical Requirements\n\nThese are NON-NEGOTIABLE. Every test run MUST satisfy ALL the following:\n\n### 1. Screenshots at Every Step\n- Take a screenshot at EVERY significant test step — not just at the end\n- Every test scenario MUST have at least one BEFORE and one AFTER screenshot\n- Name screenshots sequentially: `{NN}-{action}-{state}.png` (e.g., `01-credits-before.png`, `02-credits-after.png`)\n- If a screenshot is missing for a scenario, the test is INCOMPLETE — go back and take it\n\n### 2. Screenshots MUST Be Posted to PR\n- Attach ALL screenshots to the PR comment with `gh pr comment --attach` (see Step 7). **Never push image files to a repo branch** — attachments live in GitHub's own asset store and leave nothing behind in the repo\n- Post a PR comment with ALL screenshots embedded inline, each with its explanation\n- This is NOT optional — every test run MUST end with a PR comment containing screenshots\n- If the upload fails, retry. If it still fails, list failed files and require manual drag-and-drop/paste attachment in the PR comment, and mark the run INCOMPLETE\n\n### 3. State Verification with Before/After Evidence\n- For EVERY state-changing operation (API call, user action), capture the state BEFORE and AFTER\n- Log the actual API response values (e.g., `credits_before=100, credits_after=95`)\n- Screenshot MUST show the relevant UI state change\n- Compare expected vs actual values explicitly — do not just eyeball it\n\n### 4. Negative Test Cases Are Mandatory\n- Test at least ONE negative case per feature (e.g., insufficient credits, invalid input, unauthorized access)\n- Verify error messages are user-friendly and accurate\n- Verify the system state did NOT change after a rejected operation\n\n### 5. Test Report Must Include Full Evidence\nEach test scenario in the report MUST have:\n- **Steps**: What was done (exact commands or UI actions)\n- **Expected**: What should happen\n- **Actual**: What actually happened\n- **API Evidence**: Before/after API response values for state-changing operations\n- **Screenshot Evidence**: Before/after screenshots with explanations\n\n## State Manipulation for Realistic Testing\n\n**Billing-test trap — this one produces a false pass, not a visible failure.**\nLLM block cost filters key on the *platform-owned* credential id. A run made\nwith the test user's own API key bills nothing by design, so a credits\nbefore/after assertion silently passes on zero deltas either way. Any test\nthat verifies credit reconciliation MUST use the system credential, not a\nuser-supplied key. Related: Ollama block entries are configured with an\nexplicit `$0` run-based cost (`BlockCostType.RUN, cost_amount=0` in\n`block_cost_config.py`), not a token-metered one — so pre/post-flight cost\ndeltas are always 0 for them regardless of credential. Never use an Ollama\nmodel to test credit reconciliation; pick any hosted model billed through the\nsystem credential instead.\n\nWhen testing features that depend on specific states (rate limits, credits, quotas):\n\n1. **Use Redis CLI to set counters directly:**\n   ```bash\n   # Find the Redis container\n   REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep redis | head -1)\n   # Set a key with expiry\n   docker exec $REDIS_CONTAINER redis-cli SET key value EX ttl\n   # Example: Set rate limit counter to near-limit\n   docker exec $REDIS_CONTAINER redis-cli SET \"rate_limit:user:$PR_TEST_USER_EMAIL\" 99 EX 3600\n   # Example: Check current value\n   docker exec $REDIS_CONTAINER redis-cli GET \"rate_limit:user:$PR_TEST_USER_EMAIL\"\n   ```\n\n2. **Use API calls to check before/after state:**\n   ```bash\n   # BEFORE: Record current state\n   BEFORE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits BEFORE: $BEFORE\"\n\n   # Perform the action...\n\n   # AFTER: Record new state and compare\n   AFTER=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits AFTER: $AFTER\"\n   echo \"Delta: $(( BEFORE - AFTER ))\"\n   ```\n\n3. **Take screenshots BEFORE and AFTER state changes** — the UI must reflect the backend state change\n\n4. **Never rely on mocked/injected browser state** — always use real backend state. Do NOT use `agent-browser eval` to fake UI state. The backend must be the source of truth.\n\n5. **Use direct DB queries when needed:**\n   ```bash\n   # Query via Supabase's PostgREST or docker exec into the DB\n   docker exec supabase-db psql -U supabase_admin -d postgres -c \"SELECT credits FROM user_credits WHERE user_id = '...';\"\n   ```\n\n6. **After every API test, verify the state change actually persisted:**\n   ```bash\n   # Example: After a credits purchase, verify DB matches API\n   API_CREDITS=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   DB_CREDITS=$(docker exec supabase-db psql -U supabase_admin -d postgres -t -c \"SELECT credits FROM user_credits WHERE user_id = '...';\" | tr -d ' ')\n   [ \"$API_CREDITS\" = \"$DB_CREDITS\" ] && echo \"CONSISTENT\" || echo \"MISMATCH: API=$API_CREDITS DB=$DB_CREDITS\"\n   ```\n\n## Arguments\n\n- `$ARGUMENTS` — worktree path (e.g. `$REPO_ROOT`) or PR number\n- If `--fix` flag is present, auto-fix bugs found and push fixes (like pr-address loop)\n\n## Step 0: Resolve the target\n\n```bash\n# If argument is a PR number, find its worktree\ngh pr view {N} --json headRefName --jq '.headRefName'\n# If argument is a path, use it directly\n```\n\nDetermine:\n- `REPO_ROOT` — the root repo directory: `git -C \"$WORKTREE_PATH\" worktree list | head -1 | awk '{print $1}'` (or `git rev-parse --show-toplevel` if not a worktree)\n- `WORKTREE_PATH` — the worktree directory\n- `PLATFORM_DIR` — `$WORKTREE_PATH/autogpt_platform`\n- `BACKEND_DIR` — `$PLATFORM_DIR/backend`\n- `FRONTEND_DIR` — `$PLATFORM_DIR/frontend`\n- `PR_NUMBER` — the PR number (from `gh pr list --head $(git branch --show-current)`)\n- `PR_TITLE` — the PR title, slugified (e.g. \"Add copilot permissions\" → \"add-copilot-permissions\")\n- `RESULTS_DIR` — `$REPO_ROOT/test-results/PR-{PR_NUMBER}-{slugified-title}`\n\nCreate the results directory:\n```bash\nPR_NUMBER=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')\nPR_TITLE=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json title --jq '.[0].title' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | head -c 50)\nRESULTS_DIR=\"$REPO_ROOT/test-results/PR-${PR_NUMBER}-${PR_TITLE}\"\nmkdir -p $RESULTS_DIR\n```\n\n**Test user credentials** — required to log into the UI or call authenticated APIs.\n\nNEVER hardcode these in this SKILL, a PR comment, a screenshot, or any committed file. Sources, in priority order:\n\n1. **Env vars** (CI / preconfigured local shell): `$PR_TEST_USER_EMAIL` + `$PR_TEST_USER_PASSWORD`. If both are set, use them.\n2. **Interactive prompt** (everything else, including dev-preview runs): if the env vars are not set, ASK the user at the start of the run — e.g. \"I need test-user credentials for this run; paste the email and password.\" Hold them in shell vars only for the duration of the run. Do not echo, log, or write them to disk.\n\nAcquire the variables — env first, prompt if missing — and only then lock them in:\n\n```bash\n# 1. Prefer env vars (CI / preconfigured shell). Prompt only for the\n#    specific var that is unset so an already-exported credential is\n#    not overwritten by the prompt when only the other one is missing.\nif [ -z \"${PR_TEST_USER_EMAIL:-}\" ] || [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n  echo \"Test user credentials required for this run.\"\n  if [ -z \"${PR_TEST_USER_EMAIL:-}\" ]; then\n    read -r -p   \"Email:    \" PR_TEST_USER_EMAIL\n  fi\n  if [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n    read -r -s -p \"Password: \" PR_TEST_USER_PASSWORD\n    echo\n  fi\n  export PR_TEST_USER_EMAIL PR_TEST_USER_PASSWORD\nfi\n\n# 2. Lock them in — fail loudly if either is STILL unset (e.g. the user\n#    pressed Enter on an empty prompt). The error message names the var so\n#    the agent / operator knows what to fix.\n: \"${PR_TEST_USER_EMAIL:?PR_TEST_USER_EMAIL is empty after env+prompt — supply a value before re-running}\"\n: \"${PR_TEST_USER_PASSWORD:?PR_TEST_USER_PASSWORD is empty after env+prompt — supply a value before re-running}\"\n```\n\nFor **local docker-compose** runs, a fresh dev user is created on first call to the signup snippet below. For **dev-preview** runs, the test user lives in the project's hosted auth backend — ask the user for the current valid credentials each session (the previously-shared `test@test.com` test account was disabled on 2026-05-23 after its credentials leaked into this very SKILL — do NOT re-introduce a default). **`PR_TEST_USER_PASSWORD` should always be a throwaway/test-only credential, never a real account's password** — the auth requests in 3h go over plain HTTP on `localhost:3000` for local runs, which has no transport encryption. If a dev-preview run's target isn't on `localhost`, confirm it's HTTPS before sending credentials to it.\n\n## Step 1: Understand the PR\n\nBefore testing, understand what changed:\n\n```bash\ncd $WORKTREE_PATH\n\n# Read PR description to understand the WHY\ngh pr view {N} --json body --jq '.body'\n\ngit log --oneline dev..HEAD | head -20\ngit diff dev --stat\n```\n\nRead the PR description (Why / What / How) and changed files to understand:\n0. **Why** does this PR exist? What problem does it solve?\n1. **What** feature/fix does this PR implement?\n2. **How** does it work? What's the approach?\n3. What components are affected? (backend, frontend, copilot, executor, etc.)\n4. What are the key user-facing behaviors to test?\n\n## Step 2: Write test scenarios\n\nBased on the PR analysis, write a test plan to `$RESULTS_DIR/test-plan.md`:\n\n```markdown\n# Test Plan: PR #{N} — {title}\n\n## Scenarios\n1. [Scenario name] — [what to verify]\n2. ...\n\n## API Tests (if applicable)\n1. [Endpoint] — [expected behavior]\n   - Before state: [what to check before]\n   - After state: [what to verify changed]\n\n## UI Tests (if applicable)\n1. [Page/component] — [interaction to test]\n   - Screenshot before: [what to capture]\n   - Screenshot after: [what to capture]\n\n## Negative Tests (REQUIRED — at least one per feature)\n1. [What should NOT happen] — [how to trigger it]\n   - Expected error: [what error message/code]\n   - State unchanged: [what to verify did NOT change]\n```\n\n**Be critical** — include edge cases, error paths, and security checks. Every scenario MUST specify what screenshots to take and what state to verify.\n\n## Step 3.0: Claim the testing lock (coordinate parallel agents)\n\nMultiple worktrees share the same host — Docker infra (postgres, redis, clamav), app ports (3000/8006/…), and the test user. Two agents running `/pr-test` concurrently will corrupt each other's state (connection-pool exhaustion, port binds failing silently, cross-test assertions). Use the root-worktree lock file to take turns.\n\n### Lock file contract\n\nPath (**always** the root worktree so all siblings see it): `$REPO_ROOT/.ign.testing.lock`\n\nBody (one `key=value` per line):\n```\nholder=<pr-XXXXX-purpose>\npid=<pid-or-\"self\">\nstarted=<iso8601>\nheartbeat=<iso8601, updated every ~2 min>\nworktree=<full path>\nbranch=<branch name>\nintent=<one-line description + rough duration>\n```\n\n### Claim\n\n```bash\nLOCK=$REPO_ROOT/.ign.testing.lock\nNOW=$(date -u +%Y-%m-%dT%H:%MZ)\nSTALE_AFTER_MIN=5\n\nif [ -f \"$LOCK\" ]; then\n  HB=$(grep '^heartbeat=' \"$LOCK\" | cut -d= -f2)\n  HB_EPOCH=$(date -j -f '%Y-%m-%dT%H:%MZ' \"$HB\" +%s 2>/dev/null || date -d \"$HB\" +%s 2>/dev/null || echo 0)\n  AGE_MIN=$(( ( $(date -u +%s) - HB_EPOCH ) / 60 ))\n  if [ \"$AGE_MIN\" -gt \"$STALE_AFTER_MIN\" ]; then\n    echo \"WARN: stale lock (${AGE_MIN}m old) — reclaiming\"\n    cat \"$LOCK\" | sed 's/^/  stale: /'\n  else\n    echo \"Another agent holds the lock:\"; cat \"$LOCK\"\n    echo \"Wait until released or resume after $((STALE_AFTER_MIN - AGE_MIN))m.\"\n    exit 1\n  fi\nfi\n\ncat > \"$LOCK\" <<EOF\nholder=pr-${PR_NUMBER}-e2e\npid=self\nstarted=$NOW\nheartbeat=$NOW\nworktree=$WORKTREE_PATH\nbranch=$(cd $WORKTREE_PATH && git branch --show-current)\nintent=E2E test PR #${PR_NUMBER}, native mode, ~60min\nEOF\necho \"Lock claimed\"\n```\n\n### Heartbeat (MUST run in background during the whole test)\n\nWithout a heartbeat a crashed agent keeps the lock forever. Run this as a background process right after claim:\n\n```bash\n(while true; do\n   sleep 120\n   [ -f \"$LOCK\" ] || exit 0   # lock released → exit heartbeat\n   perl -i -pe \"s/^heartbeat=.*/heartbeat=$(date -u +%Y-%m-%dT%H:%MZ)/\" \"$LOCK\"\n done) &\nHEARTBEAT_PID=$!\necho \"$HEARTBEAT_PID\" > /tmp/pr-test-heartbeat.pid\n```\n\n### Release (always — even on failure)\n\n```bash\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\nUse a `trap` so release runs even on `exit 1`:\n```bash\ntrap 'kill \"$HEARTBEAT_PID\" 2>/dev/null; rm -f \"$LOCK\"' EXIT INT TERM\n```\n\n### **Release the lock AS SOON AS the test run is done**\n\nThe lock guards **test execution**, not **app lifecycle**. Once Step 5 (record results) and Step 6 (post PR comment) are complete, release the lock IMMEDIATELY — even if:\n\n- The native `poetry run app` / `pnpm dev` processes are still running so the user can keep poking at the app manually.\n- You're leaving docker containers up.\n- You're tailing logs for a minute or two.\n\nKeeping the lock held past the test run is the single most common way `/pr-test` stalls other agents. **The app staying up is orthogonal to the lock; don't conflate them.** Sibling worktrees running their own `/pr-test` will kill the stray processes and free the ports themselves (Step 3c/3e-native handle that) — they just need the lock file gone.\n\nConcretely, the sequence at the end of every `/pr-test` run (success or failure) is:\n\n```bash\n# 1. Write the final report + post PR comment — done above in Step 5/6.\n# 2. Release the lock right now, even if the app is still up.\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock (app may still be running)\" \\\n    >> $REPO_ROOT/.ign.testing.log\n# 3. Optionally leave the app running and note it so the user knows:\necho \"Native stack still running on :3000 / :8006 for manual poking. Kill with:\"\necho \"  pkill -9 -f 'poetry run app'; pkill -9 -f 'next-server|next dev'\"\n```\n\nIf a sibling agent's `/pr-test` needs to take over, it'll do the kill+rebuild dance from Step 3c/3e-native on its own — your only job is to not hold the lock file past the end of your test.\n\n### Shared status log\n\n`$REPO_ROOT/.ign.testing.log` is an append-only channel any agent can read/write. Use it for \"I'm waiting\", \"I'm done, resources free\", or post-run notes:\n```bash\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] <message>\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\n## Step 3: Environment setup\n\n### 3a. Copy .env files from the root worktree\n\nThe root worktree (`$REPO_ROOT`) has the canonical `.env` files with all API keys. Copy them to the target worktree:\n\n```bash\n# CRITICAL: .env files are NOT checked into git. They must be copied manually.\ncp $REPO_ROOT/autogpt_platform/.env $PLATFORM_DIR/.env\ncp $REPO_ROOT/autogpt_platform/backend/.env $BACKEND_DIR/.env\ncp $REPO_ROOT/autogpt_platform/frontend/.env $FRONTEND_DIR/.env\n```\n\n**A copy from the root worktree is no longer sufficient on a recent `dev`** —\nauth moved from Supabase to Better Auth (see 3h) and two vars are easy to\nmiss because nothing fails loudly without them, it just 401s later:\n\n- `$BACKEND_DIR/.env` needs `JWT_JWKS_URL` — the Better Auth JWKS endpoint the\n  backend verifies tokens against. The `localhost:3000` value below is for\n  **native mode only**. In docker mode it's harmless to have it in `.env`\n  because `docker-compose.platform.yml` overrides it with the\n  Compose-reachable `http://frontend:3000/api/auth/jwks` — but if you ever run\n  the backend against this `.env` value directly (bypassing Compose), a\n  `localhost` value inside a container resolves to itself, not the frontend,\n  and every call 401s with no other symptom.\n- `$FRONTEND_DIR/.env` needs its **own** `DATABASE_URL` — Better Auth runs\n  inside the Next.js app and talks to Postgres directly, it does not go\n  through the backend. **Derive it from `$BACKEND_DIR/.env`'s `DB_USER` /\n  `DB_PASS` / `DB_PORT` / `DB_NAME` rather than copying\n  `frontend/.env.default`'s placeholder verbatim** — the placeholder happens\n  to match the stock local defaults, but if `backend/.env`'s `DB_PASS` was\n  ever customized (rotated secret, non-default port), copying the placeholder\n  silently points Better Auth at the wrong database instead of the one the\n  rest of the stack actually uses.\n\n```bash\n# [ -n ... ], not grep -q alone — a present-but-empty JWT_JWKS_URL= would\n# otherwise be treated as \"already set\" and skip the fallback, leaving the\n# backend without a JWKS endpoint to verify tokens against.\n[ -n \"$(grep '^JWT_JWKS_URL=' $BACKEND_DIR/.env | cut -d= -f2-)\" ] || echo \"JWT_JWKS_URL=http://localhost:3000/api/auth/jwks\" >> $BACKEND_DIR/.env  # native mode only — see note above\n\nif [ -n \"$(grep '^DATABASE_URL=' $FRONTEND_DIR/.env | cut -d= -f2-)\" ]; then\n  # grep -q alone matches a present-but-empty DATABASE_URL= too, which would\n  # otherwise skip derivation and leave Better Auth pointed at nothing.\n  echo \"Frontend DATABASE_URL: already set (not touching it)\"\nelse\n  # cut -f2 (not -f2-) truncates any value containing '=' (base64 secrets do); -f2- keeps the rest.\n  DB_USER=$(grep '^DB_USER=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PASS=$(grep '^DB_PASS=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PORT=$(grep '^DB_PORT=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_NAME=$(grep '^DB_NAME=' $BACKEND_DIR/.env | cut -d= -f2-)\n  : \"${DB_USER:?}\" \"${DB_PASS:?}\" \"${DB_PORT:?}\" \"${DB_NAME:?}\"  # fail loudly, not with a silently-empty URL\n  # Percent-encode user/pass — a raw '@', '#', '?', '%', or ':' in either would\n  # otherwise be misparsed as URL structure instead of credential content.\n  # Via env vars, not `jq --arg`, which would put DB_PASS in the process arglist.\n  DB_USER_ENC=$(DB_USER_VAL=\"$DB_USER\" jq -rn '$ENV.DB_USER_VAL|@uri')\n  DB_PASS_ENC=$(DB_PASS_VAL=\"$DB_PASS\" jq -rn '$ENV.DB_PASS_VAL|@uri')\n  echo \"DATABASE_URL=postgresql://${DB_USER_ENC}:${DB_PASS_ENC}@localhost:${DB_PORT}/${DB_NAME}\" >> $FRONTEND_DIR/.env\n  # Reconstructed, not regex-redacted — a password containing '@' would otherwise\n  # leak its tail past a naive \"redact up to the first @\" pattern.\n  echo \"Frontend DATABASE_URL: postgresql://${DB_USER_ENC}:***@localhost:${DB_PORT}/${DB_NAME}\"\nfi\n```\n\n### 3b. Configure copilot authentication\n\nThe copilot needs an LLM API to function. Two approaches (try subscription first):\n\n#### Option 1: Subscription mode (preferred — uses your Claude Max/Pro subscription)\n\nThe `claude_agent_sdk` Python package **bundles its own Claude CLI binary** — no need to install `@anthropic-ai/claude-code` via npm. The backend auto-provisions credentials from environment variables on startup.\n\nRun the helper script to extract tokens from your host and auto-update `backend/.env` (works on macOS, Linux, and Windows/WSL):\n\n```bash\n# Extracts OAuth tokens and writes CLAUDE_CODE_OAUTH_TOKEN + CLAUDE_CODE_REFRESH_TOKEN into .env\nbash $BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env\n```\n\n**How it works:** The script reads the OAuth token from:\n- **macOS**: system keychain (`\"Claude Code-credentials\"`)\n- **Linux/WSL**: `~/.claude/.credentials.json`\n- **Windows**: `%APPDATA%/claude/.credentials.json`\n\nIt sets `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REFRESH_TOKEN`, and `CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true` in the `.env` file. On container startup, the backend auto-provisions `~/.claude/.credentials.json` inside the container from these env vars. The SDK's bundled CLI then authenticates using that file. No `claude login`, no npm install needed.\n\n**Note:** The OAuth token expires (~24h). If copilot returns auth errors, re-run the script and restart: `$BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env && docker compose up -d copilot_executor`\n\n#### Option 2: OpenRouter API key mode (fallback)\n\nIf subscription mode doesn't work, switch to API key mode using OpenRouter:\n\n```bash\n# In $BACKEND_DIR/.env, ensure these are set:\nCHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false\nCHAT_API_KEY=<value of OPEN_ROUTER_API_KEY from the same .env>\nCHAT_BASE_URL=https://openrouter.ai/api/v1\nCHAT_USE_CLAUDE_AGENT_SDK=true\n```\n\nUse `sed` to update these values:\n```bash\nORKEY=$(grep \"^OPEN_ROUTER_API_KEY=\" $BACKEND_DIR/.env | cut -d= -f2)\n[ -n \"$ORKEY\" ] || { echo \"ERROR: OPEN_ROUTER_API_KEY is missing in $BACKEND_DIR/.env\"; exit 1; }\nperl -i -pe 's/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false/' $BACKEND_DIR/.env\n# Add or update CHAT_API_KEY and CHAT_BASE_URL\ngrep -q \"^CHAT_API_KEY=\" $BACKEND_DIR/.env && perl -i -pe \"s|^CHAT_API_KEY=.*|CHAT_API_KEY=$ORKEY|\" $BACKEND_DIR/.env || echo \"CHAT_API_KEY=$ORKEY\" >> $BACKEND_DIR/.env\ngrep -q \"^CHAT_BASE_URL=\" $BACKEND_DIR/.env && perl -i -pe 's|^CHAT_BASE_URL=.*|CHAT_BASE_URL=https://openrouter.ai/api/v1|' $BACKEND_DIR/.env || echo \"CHAT_BASE_URL=https://openrouter.ai/api/v1\" >> $BACKEND_DIR/.env\n```\n\n### 3c. Stop conflicting containers\n\n```bash\n# Stop any running app containers (keep infra: supabase, redis, rabbitmq, clamav)\ndocker ps --format \"{{.Names}}\" | grep -E \"rest_server|executor|copilot|websocket|database_manager|scheduler|notification|frontend|migrate\" | while read name; do\n  docker stop \"$name\" 2>/dev/null\ndone\n```\n\n**Native mode also:** when running the app natively (see 3e-native), kill any stray host processes and free the app ports before starting — otherwise `poetry run app` and `pnpm dev` will fail to bind.\n\n**Kill by port, not by broad process pattern.** A pattern-based\n`pkill -f \"python.*backend\"` (or anything matching by worktree cwd) is too\ncoarse on a host running several worktrees — it has taken out the frontend\nand a mock server sitting on other ports along with the intended backend\nprocess. Target the pid actually holding each port instead — this only kills\nwhoever is bound to that specific port, which is narrower than a pattern\nmatch, but **it is not worktree isolation**: if a sibling worktree's own dev\nserver happens to be using one of these ports (e.g. its frontend also on\n:3000), this kills that too. `lsof` tells you who holds the port, not who\nowns it — a `docker-proxy` pid there means a compose stack owns it.\n\n```bash\n# Free app ports one at a time — errors per port are ignored (port may simply\n# be unused). `xargs -r` is GNU-only (macOS xargs rejects -r); the `[ -n ]`\n# guard below is the portable equivalent.\nfor port in 3000 8006 8001 8002 8005 8008; do\n  pids=$(lsof -ti :$port -sTCP:LISTEN 2>/dev/null)\n  [ -n \"$pids\" ] && kill -9 $pids 2>/dev/null || true\ndone\n```\n\n### 3e-native. Run the app natively (PREFERRED for iterative dev)\n\nNative mode runs infra (postgres, supabase, redis, rabbitmq, clamav) in docker but runs the backend and frontend directly on the host. This avoids the 3-8 minute `docker compose build` cycle on every backend change — code edits are picked up on process restart (seconds) instead of a full image rebuild.\n\n**When to prefer native mode (default for this skill):**\n- Iterative dev/debug loops where you're editing backend or frontend code between test runs\n- Any PR that touches Python/TS source but not Dockerfiles, compose config, or infra images\n- Fast repro of a failing scenario — restart `poetry run app` in a couple of seconds\n\n**When to prefer docker mode (3e fallback):**\n- Testing changes to `Dockerfile`, `docker-compose.yml`, or base images\n- Production-parity smoke tests (exact container env, networking, volumes)\n- CI-equivalent runs where you need the exact image that'll ship\n\n**Note on 3b (copilot auth):** no npm install anywhere. `poetry install` pulls in `claude_agent_sdk`, which ships its own Claude CLI binary — available on `PATH` whenever you run commands via `poetry run` (native) OR whenever the copilot_executor container is built from its Poetry lockfile (docker). The OAuth token extraction still applies (same `refresh_claude_token.sh` call).\n\n**Preamble:** before starting native, run the kill-stray + free-ports block from 3c's \"Native mode also\" subsection.\n\n**1. Start infra only (one-time per session):**\n\n```bash\ncd $PLATFORM_DIR && docker compose --profile local up deps --detach --remove-orphans --build\n```\n\nThis brings up postgres/supabase/redis/rabbitmq/clamav and skips all app services.\n\n**2. Start the backend natively:**\n\n```bash\ncd $BACKEND_DIR && (poetry run app 2>&1 | tee .ign.application.logs) &\n```\n\n`poetry run app` spawns **all** app subprocesses — `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, `database_manager` — inside ONE parent process. No separate containers, no separate terminals. The `.ign.application.logs` prefix is already gitignored.\n\n**3. Wait for the backend on :8006 BEFORE starting the frontend.** This ordering matters — the frontend's `pnpm dev` startup invokes `generate-api-queries`, which fetches `/openapi.json` from the backend. If the backend isn't listening yet, `pnpm dev` fails immediately.\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8006/docs 2>/dev/null)\" = \"200\" ]; then\n    echo \"Backend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\n**4. Start the frontend natively:**\n\n```bash\ncd $FRONTEND_DIR && (pnpm dev 2>&1 | tee .ign.frontend.logs) &\n```\n\n**5. Wait for the frontend on :3000:**\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000 2>/dev/null)\" = \"200\" ]; then\n    echo \"Frontend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\nOnce both are up, skip 3e/3f and go straight to **3g/3h** (feature flags / test user creation).\n\n### 3e. Build and start (docker — fallback)\n\n```bash\ncd $PLATFORM_DIR && docker compose build --no-cache 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker build failed\"; exit 1; fi\n\ncd $PLATFORM_DIR && docker compose up -d 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker compose up failed\"; exit 1; fi\n```\n\n**Note:** If the container appears to be running old code (e.g. missing PR changes), use `docker compose build --no-cache` to force a full rebuild. Docker BuildKit may sometimes reuse cached `COPY` layers from a previous build on a different branch.\n\n**Expected time: 3-8 minutes** for build, 5-10 minutes with `--no-cache`.\n\n### 3f. Wait for services to be ready\n\n```bash\n# Poll until backend and frontend respond\nfor i in $(seq 1 60); do\n  BACKEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8006/docs 2>/dev/null)\n  FRONTEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000 2>/dev/null)\n  if [ \"$BACKEND\" = \"200\" ] && [ \"$FRONTEND\" = \"200\" ]; then\n    echo \"Services ready\"\n    break\n  fi\n  sleep 5\ndone\n```\n\n\n### 3h. Create test user and get auth token\n\nThe platform moved off Supabase auth to Better Auth, embedded in the\nNext.js app at `/api/auth/*`. Signup and sign-in both go through the frontend\nnow, not Kong on :8000 — and `/api/auth/token` mints a backend-API JWT from a\n**session cookie**, it does not accept credentials directly, so sign-in has to\nhappen first to get that cookie.\n\nBetter Auth's default minimum password length is **12 characters** — shorter\nvalues fail signup with `PASSWORD_TOO_SHORT` and every step below degrades\nsilently into an empty token unless you check for it.\n\n```bash\nCOOKIE_JAR=$(mktemp)\ntrap 'rm -f \"$COOKIE_JAR\"' EXIT  # cleans up on early exit too, not just the happy path\n# Via env vars, not `jq --arg`, which would put the password in the process arglist.\nAUTH_PAYLOAD=$(PR_TEST_USER_EMAIL=\"$PR_TEST_USER_EMAIL\" PR_TEST_USER_PASSWORD=\"$PR_TEST_USER_PASSWORD\" \\\n  jq -nc '{email:$ENV.PR_TEST_USER_EMAIL,password:$ENV.PR_TEST_USER_PASSWORD,name:\"PR Test User\"}')\n\n# Signup (idempotent — a real error body means \"already exists\" only if you\n# check; -d passes the payload as an argument, which leaks the password into\n# process listings, so pipe it through stdin with --data-binary @- instead.\n# --noproxy guards against an inherited proxy env var routing the password\n# through a proxy. --max-time bounds it — without one, a server that accepts\n# the connection but never responds hangs setup indefinitely instead of\n# reaching the empty-$TOKEN failure check below).\nSIGNUP_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -X POST 'http://localhost:3000/api/auth/sign-up/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNUP_RESULT\" | grep -qi '\"code\"' && echo \"Signup: $SIGNUP_RESULT\"  # log it — \"already exists\" and \"password too short\" look identical downstream otherwise\n\n# Sign in — sets the better-auth.session_token cookie in $COOKIE_JAR.\n# Capture the body: a failure here (e.g. account exists with a different\n# password) otherwise only shows up as an empty $TOKEN with no explanation.\nSIGNIN_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -c \"$COOKIE_JAR\" -X POST 'http://localhost:3000/api/auth/sign-in/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNIN_RESULT\" | grep -qi '\"code\"' && echo \"Sign-in: $SIGNIN_RESULT\"\n\n# Mint a backend-API JWT from the session cookie\nTOKEN=$(curl -s -b \"$COOKIE_JAR\" 'http://localhost:3000/api/auth/token' | jq -r '.token // \"\"')\n[ -n \"$TOKEN\" ] || { echo \"ERROR: auth setup failed — TOKEN is empty. Check password length (min 12 chars), AUTH_ALLOW_NEW_ACCOUNTS, AUTH_SIGNUP_ALLOWLIST, and rate limiting on /api/auth/*.\"; exit 1; }\n```\n\n**Use this token for ALL API calls:**\n```bash\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/...\n```\n\n### 3i. Disable onboarding for test user\n\nThe frontend redirects to `/onboarding` when the `ONBOARDING_COMPLETE` step is not in `completedSteps`.\nMark it complete via the backend API so every browser test lands on the real feature UI:\n\n```bash\nONBOARDING_RESULT=$(curl -s --max-time 30 -X POST \\\n  \"http://localhost:8006/api/onboarding/step?step=ONBOARDING_COMPLETE\" \\\n  -H \"Authorization: Bearer $TOKEN\")\necho \"Onboarding bypass: $ONBOARDING_RESULT\"\n\n# Verify it took effect\nONBOARDING_STATUS=$(curl -s --max-time 30 \\\n  \"http://localhost:8006/api/onboarding/completed\" \\\n  -H \"Authorization: Bearer $TOKEN\" | jq -r '.is_completed')\necho \"Onboarding completed: $ONBOARDING_STATUS\"\nif [ \"$ONBOARDING_STATUS\" != \"true\" ]; then\n  echo \"ERROR: onboarding bypass failed — browser tests will hit /onboarding instead of the target feature. Investigate before proceeding.\"\n  exit 1\nfi\n```\n\n## Step 4: Run tests\n\n### Service ports reference\n\n| Service | Port | URL |\n|---------|------|-----|\n| Frontend | 3000 | http://localhost:3000 |\n| Backend REST | 8006 | http://localhost:8006 |\n| Supabase Auth (via Kong) | 8000 | http://localhost:8000 |\n| Executor (metrics only) | 8002 | http://localhost:8002 in native mode; not published by Docker Compose |\n| Copilot Executor (metrics only) | 8008 | http://localhost:8008 in native mode; not published by Docker Compose |\n| WebSocket | 8001 | http://localhost:8001 |\n| Database Manager | 8005 | http://localhost:8005 in native mode; not published by Docker Compose. Every method needs `Authorization: Bearer <internal key>` (`backend/util/service_auth.py`) |\n| Redis | 6379 | localhost:6379 |\n| RabbitMQ | 5672 | localhost:5672 |\n\n### API testing\n\nUse `curl` with the auth token for backend API tests. **For EVERY API call that changes state, record before/after values:**\n\n```bash\n# Example: List agents\ncurl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/graphs | jq . | head -20\n\n# Example: Create an agent\ncurl -s -X POST http://localhost:8006/api/graphs \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{...}' | jq .\n\n# Example: Run an agent\ncurl -s -X POST \"http://localhost:8006/api/graphs/{graph_id}/execute\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"data\": {...}}'\n\n# Example: Get execution results\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:8006/api/graphs/{graph_id}/executions/{exec_id}\" | jq .\n```\n\n**State verification pattern (use for EVERY state-changing API call):**\n```bash\n# 1. Record BEFORE state\nBEFORE_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"BEFORE: $BEFORE_STATE\"\n\n# 2. Perform the action\nACTION_RESULT=$(curl -s -X POST ... | jq .)\necho \"ACTION RESULT: $ACTION_RESULT\"\n\n# 3. Record AFTER state\nAFTER_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"AFTER: $AFTER_STATE\"\n\n# 4. Log the comparison\necho \"=== STATE CHANGE VERIFICATION ===\"\necho \"Before: $BEFORE_STATE\"\necho \"After: $AFTER_STATE\"\necho \"Expected change: {describe what should have changed}\"\n```\n\n### Mock-provider pattern (deterministic, $0)\n\nFor timeout/latency/error-handling behavior that would otherwise need a real\nLLM call, point the OpenAI SDK at a local mock instead — it honours\n`OPENAI_BASE_URL`, so a small local Responses-API server can stand in for the\nprovider while everything downstream (executor, credentials, billing) still\nruns for real. This proved out 4/4 test items at $0 in this run. **This\ncovers LLM blocks (`providers.py`) and the Codex block** — the copilot's own\nLLM calls go through `backend/util/clients.py`, which passes `base_url`\nexplicitly and does not read `OPENAI_BASE_URL`, so this trick doesn't reach\ncopilot chat.\n\n```bash\n# In $BACKEND_DIR/.env, point the OpenAI provider at a local mock server\n# that implements the subset of the Responses API you need (e.g. delayed\n# responses to test timeout handling, or a 500 to test error surfacing).\n# Restart the backend after changing this — it's read at startup.\nOPENAI_BASE_URL=http://localhost:{mock_port}/v1\n```\n\n**Use a throwaway/dummy provider credential with the mock, never the system\ncredential** — only the transport is faked, so the credential lookup still\nruns for real and whatever key you configure gets sent as a header to your\nlocal mock server. A dummy key also means the mock server's logs (which may\nend up pasted into a PR comment) can't leak a real one. Aside from the\ncredential, this is safe to use for anything that isn't itself testing model\n*output* quality.\n\nIn **docker mode**, `localhost` inside the backend container isn't reachable\nfrom your host-side mock server — point `OPENAI_BASE_URL` at a\nCompose-reachable hostname instead (or `host.docker.internal` with a\n`host-gateway` entry), and set it before `docker compose up` or restart the\naffected services after changing `$BACKEND_DIR/.env`.\n\nKeep the mock server's own response timeout short — the OpenAI SDK's default\nclient timeout is 600s, so a hung mock stalls every LLM-backed block for that\nlong instead of failing fast.\n\n### Browser testing with agent-browser\n\nPrimary tool — use this wherever `agent-browser` is installed:\n\n```bash\n# Close any existing session\nagent-browser close 2>/dev/null || true\n\n# Use --session-name to persist cookies across navigations\n# This means login only needs to happen once per test session\nagent-browser --session-name pr-test open 'http://localhost:3000/login' --timeout 15000\n\n# Get interactive elements\nagent-browser --session-name pr-test snapshot | grep \"textbox\\|button\"\n\n# Login (read creds from env — set PR_TEST_USER_EMAIL / PR_TEST_USER_PASSWORD or ask the user)\nagent-browser --session-name pr-test fill {email_ref} \"$PR_TEST_USER_EMAIL\"\nagent-browser --session-name pr-test fill {password_ref} \"$PR_TEST_USER_PASSWORD\"\nagent-browser --session-name pr-test click {login_button_ref}\nsleep 5\n\n# Dismiss cookie banner if present\nagent-browser --session-name pr-test click 'text=Accept All' 2>/dev/null || true\n\n# Navigate — cookies are preserved so login persists\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n\n# Take screenshot\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/01-page.png\n\n# Interact with elements\nagent-browser --session-name pr-test fill {ref} \"text\"\nagent-browser --session-name pr-test press \"Enter\"\nagent-browser --session-name pr-test click {ref}\nagent-browser --session-name pr-test click 'text=Button Text'\n\n# Read page content\nagent-browser --session-name pr-test snapshot | grep \"text:\"\n```\n\n**Key pages:**\n- `/copilot` — CoPilot chat (for testing copilot features)\n- `/build` — Agent builder (for testing block/node features)\n- `/build?flowID={id}` — Specific agent in builder\n- `/library` — Agent library (for testing listing/import features)\n- `/library/agents/{id}` — Agent detail with run history\n- `/marketplace` — Marketplace\n\n**Fallback — if `agent-browser` isn't installed on the host, don't let `npx`\ndownload it.** Use `@playwright/test` instead — the package is already in the\nfrontend's `node_modules`, but **the Chromium binary itself is not**;\nPlaywright caches browser binaries separately (`~/.cache/ms-playwright/` by\ndefault) and nothing installs them automatically. Run\n`pnpm exec playwright install chromium` once per host if `chromium.launch()`\nfails looking for an executable, then use it for finer control over timing\n(`waitForSelector`, explicit timeouts) than agent-browser's CLI gives you:\n\n```bash\ncd $FRONTEND_DIR  # required — @playwright/test is only declared here, not at the repo root\n# Check the installer's own exit status, not grep's — piping through grep to\n# drop blank lines would otherwise swallow a real install failure and let\n# chromium.launch() below fail later with a much less clear error.\npnpm exec playwright install chromium\n[ $? -eq 0 ] || { echo \"ERROR: playwright install chromium failed\"; exit 1; }\n\nnode -e \"\nconst { chromium } = require('@playwright/test');\n(async () => {\n  const browser = await chromium.launch();\n  try {\n    const page = await browser.newPage();\n    await page.goto('http://localhost:3000/login');\n    await page.screenshot({ path: '$RESULTS_DIR/01-login.png' });\n  } finally {\n    await browser.close();  // otherwise a goto timeout leaks a headless Chromium process\n  }\n})();\n\"\n```\n\n### Checking logs\n\n**Native mode:** when running via `poetry run app` + `pnpm dev`, all app logs stream to the `.ign.*.logs` files written by the `tee` pipes in 3e-native. `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, and `database_manager` are all subprocesses of the single `poetry run app` parent, so their output is interleaved in `.ign.application.logs`.\n\n```bash\n# Backend (all app subprocesses interleaved)\ntail -f $BACKEND_DIR/.ign.application.logs\n\n# Frontend (Next.js dev server)\ntail -f $FRONTEND_DIR/.ign.frontend.logs\n\n# Filter for errors across either log\ngrep -iE \"error|exception|traceback\" $BACKEND_DIR/.ign.application.logs | tail -20\ngrep -iE \"error|exception|traceback\" $FRONTEND_DIR/.ign.frontend.logs | tail -20\n```\n\n**Docker mode:**\n\n```bash\n# Backend REST server\ndocker logs autogpt_platform-rest_server-1 2>&1 | tail -30\n\n# Executor (runs agent graphs)\ndocker logs autogpt_platform-executor-1 2>&1 | tail -30\n\n# Copilot executor (runs copilot chat sessions)\ndocker logs autogpt_platform-copilot_executor-1 2>&1 | tail -30\n\n# Frontend\ndocker logs autogpt_platform-frontend-1 2>&1 | tail -30\n\n# Filter for errors\ndocker logs autogpt_platform-executor-1 2>&1 | grep -i \"error\\|exception\\|traceback\" | tail -20\n```\n\n### Copilot chat testing\n\nThe copilot uses SSE streaming. To test via API:\n\n```bash\n# Create a session\nSESSION_ID=$(curl -s -X POST 'http://localhost:8006/api/chat/sessions' \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}' | jq -r '.id // .session_id // \"\"')\n\n# Stream a message (SSE - will stream chunks)\ncurl -N -X POST \"http://localhost:8006/api/chat/sessions/$SESSION_ID/stream\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"message\": \"Hello, what can you help me with?\"}' \\\n  --max-time 60 2>/dev/null | head -50\n```\n\nOr test via browser (preferred for UI verification):\n```bash\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n# ... fill chat input and press Enter, wait 20-30s for response\n```\n\n## Step 5: Record results and take screenshots\n\n**Take a screenshot at EVERY significant test step** — before and after interactions, on success, and on failure. This is NON-NEGOTIABLE.\n\n**Required screenshot pattern for each test scenario:**\n```bash\n# BEFORE the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-before.png\n\n# Perform the action...\n\n# AFTER the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-after.png\n```\n\n**Naming convention:**\n```bash\n# Examples:\n# $RESULTS_DIR/01-login-page-before.png\n# $RESULTS_DIR/02-login-page-after.png\n# $RESULTS_DIR/03-credits-page-before.png\n# $RESULTS_DIR/04-credits-purchase-after.png\n# $RESULTS_DIR/05-negative-insufficient-credits.png\n# $RESULTS_DIR/06-error-state.png\n```\n\n**Minimum requirements:**\n- At least TWO screenshots per test scenario (before + after)\n- At least ONE screenshot for each negative test case showing the error state\n- If a test fails, screenshot the failure state AND any error logs visible in the UI\n\n## Step 6: Show results to user with screenshots\n\n**CRITICAL: After all tests complete, you MUST show every screenshot to the user using the Read tool, with an explanation of what each screenshot shows.** This is the most important part of the test report — the user needs to visually verify the results.\n\nFor each screenshot:\n1. Use the `Read` tool to display the PNG file (Claude can read images)\n2. Write a 1-2 sentence explanation below it describing:\n   - What page/state is being shown\n   - What the screenshot proves (which test scenario it validates)\n   - Any notable details visible in the UI\n\nFormat the output like this:\n\n```markdown\n### Screenshot 1: {descriptive title}\n[Read the PNG file here]\n\n**What it shows:** {1-2 sentence explanation of what this screenshot proves}\n\n---\n```\n\nAfter showing all screenshots, output a **detailed** summary table:\n\n| # | Scenario | Result | API Evidence | Screenshot Evidence |\n|---|----------|--------|-------------|-------------------|\n| 1 | {name} | PASS/FAIL | Before: X, After: Y | 01-before.png, 02-after.png |\n| 2 | ... | ... | ... | ... |\n\n**IMPORTANT:** As you show each screenshot and record test results, persist them in shell variables for Step 7:\n\n```bash\n# Build these variables during Step 6 — they are required by Step 7's script\n# NOTE: declare -A requires Bash 4.0+. This is standard on modern systems (macOS ships zsh\n# but Homebrew bash is 5.x; Linux typically has bash 5.x). If running on Bash <4, use a\n# plain variable with a lookup function instead.\ndeclare -A SCREENSHOT_EXPLANATIONS=(\n  [\"01-login-page.png\"]=\"Shows the login page loaded successfully with SSO options visible.\"\n  [\"02-builder-with-block.png\"]=\"The builder canvas displays the newly added block connected to the trigger.\"\n  # ... one entry per screenshot, using the same explanations you showed the user above\n)\n\nTEST_RESULTS_TABLE=\"| 1 | Login flow | PASS | N/A | 01-login-before.png, 02-login-after.png |\n| 2 | Credits purchase | PASS | Before: 100, After: 95 | 03-credits-before.png, 04-credits-after.png |\n| 3 | Insufficient credits (negative) | PASS | Credits: 0, rejected | 05-insufficient-credits-error.png |\"\n# ... one row per test scenario with actual results\n```\n\n## Step 7: Post test report as PR comment with screenshots\n\nPost the report with `gh pr comment --attach`. Each attached file is uploaded to GitHub's own asset store, and any image the body already references by that path is rewritten to point at the uploaded asset. Screenshots never touch a repo branch.\n\n**This step is MANDATORY. Every test run MUST post a PR comment — with the screenshots attached, or an image-free INCOMPLETE report when attachment is unavailable (see the fallback below). Never nothing.**\n\n**Attachments are permanent.** A `user-attachments` asset stays reachable by anyone holding its URL regardless of repo visibility, and deleting the comment does not reliably revoke it — there is no undo. Look at every screenshot for credentials, tokens, or customer data before this step runs.\n\n**Requires `gh` >= 2.99.0** — `--attach` landed there. Distro packages lag (Fedora's rpm tops out at 2.97.0), so check the version you are actually running before building the body:\n\n```bash\n# Too-old gh is not fatal here: the report still gets posted, just without\n# images, through the same fallback path a failed upload takes (below).\nGH_VERSION=$(gh version 2>/dev/null | head -1 | awk '{print $3}')\n# Numeric compare, not `sort -V`: BSD sort on macOS may lack -V, and an empty\n# substitution would silently read as \"too old\" on a perfectly good gh.\nGH_MAJOR=${GH_VERSION%%.*}; GH_REST=${GH_VERSION#*.}; GH_MINOR=${GH_REST%%.*}\nATTACH_OK=0\nATTACH_SKIP_REASON=\"\"\nif [ \"${GH_MAJOR:-0}\" -gt 2 ] 2>/dev/null || { [ \"${GH_MAJOR:-0}\" -eq 2 ] && [ \"${GH_MINOR:-0}\" -ge 99 ]; } 2>/dev/null; then\n  ATTACH_OK=1\nelse\n  ATTACH_SKIP_REASON=\"gh ${GH_VERSION:-not found} is below 2.99.0 — upgrade gh\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\nfi\n```\n\n**CRITICAL — NEVER post a bare directory link like `https://github.com/.../tree/...`.** Every screenshot MUST appear as `![name](url)` inline in the PR comment so reviewers can see them without clicking any links. After posting, the verification step below counts the rewritten asset URLs against the number of screenshots and exits 1 on any shortfall — the test run is incomplete until this passes.\n\n**CRITICAL — NEVER paste absolute local paths or credentials into the PR comment.** Strings like `/Users/…`, `/home/…`, `C:\\…` are useless to every reviewer except you, and a bearer token or JWT copied from the live stack's evidence is a real leak in a public comment. The build block below runs an egress check on the exact bytes about to be posted and aborts on either. The `./01-shot.png` references the attachments use are relative, not local paths, and are rewritten on upload — they are fine. Keep local paths in `$RESULTS_DIR/test-report.md` for yourself; only copy the *content* they reference (excerpts, test names, log lines) into the PR comment, not the path.\n\nBuild the body with **relative image references**, then attach the same paths:\n\n```bash\nREPO=\"Significant-Gravitas/AutoGPT\"\nMAX_ATTACHMENTS=50   # per gh pr comment invocation\n\n# Fail closed: an empty RESULTS_DIR would glob the PR worktree's PNGs into a public comment.\n# gh matches --attach to the body by exact path, so the cd is required; undone at the end.\n[ -n \"$RESULTS_DIR\" ] && [ -d \"$RESULTS_DIR\" ] || { echo \"ERROR: RESULTS_DIR is unset or not a directory: '$RESULTS_DIR'\"; exit 1; }\nSTEP7_ORIG_DIR=$PWD\ncd \"$RESULTS_DIR\" || exit 1\nNULLGLOB_WAS=$(shopt -p nullglob)   # restore the caller's setting, don't force it off\nshopt -s nullglob\nSCREENSHOT_FILES=(*.png)\n$NULLGLOB_WAS\nif [ ${#SCREENSHOT_FILES[@]} -eq 0 ]; then\n  echo \"ERROR: No screenshots found in $RESULTS_DIR. Test run is incomplete.\"\n  exit 1\nfi\n# Over the cap, post the report without images rather than nothing at all.\nif [ ${#SCREENSHOT_FILES[@]} -gt \"$MAX_ATTACHMENTS\" ]; then\n  ATTACH_SKIP_REASON=\"${#SCREENSHOT_FILES[@]} screenshots exceed the ${MAX_ATTACHMENTS}-attachment limit per comment\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\n  ATTACH_OK=0\nfi\n\nIMAGE_MARKDOWN=\"\"\nATTACH_ARGS=()\nfor BASENAME in \"${SCREENSHOT_FILES[@]}\"; do\n  TITLE=$(echo \"${BASENAME%.png}\" | sed 's/^[0-9]*-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')\n  EXPLANATION=\"${SCREENSHOT_EXPLANATIONS[$BASENAME]}\"\n  if [ -z \"$EXPLANATION\" ]; then\n    echo \"ERROR: Missing screenshot explanation for $BASENAME. Add it to SCREENSHOT_EXPLANATIONS in Step ","AGENTS.md":"# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/AGENTS.md)","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/CLAUDE.md)",".github/copilot-instructions.md":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.github/copilot-instructions.md)","autogpt_platform/AGENTS.md":"# autogpt_platform - AI Agent Protocol & Instructions\n\nPath: `autogpt_platform/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/AGENTS.md)","autogpt_platform/CLAUDE.md":"# autogpt_platform - Claude Agent Guidelines & System Prompt\n\nPath: `autogpt_platform/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/CLAUDE.md)","classic/CLAUDE.md":"# classic - Claude Agent Guidelines & System Prompt\n\nPath: `classic/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/CLAUDE.md)","docs/AGENTS.md":"# docs - AI Agent Protocol & Instructions\n\nPath: `docs/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/AGENTS.md)","docs/CLAUDE.md":"# docs - Claude Agent Guidelines & System Prompt\n\nPath: `docs/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/CLAUDE.md)",".claude/skills/vercel-react-best-practices/AGENTS.md":"# Vercel-react-best-practices Skill\n\nPath: `.claude/skills/vercel-react-best-practices/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/AGENTS.md)","autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json)","autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json)","autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json)","autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json)","autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json)","autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json)","autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json)","autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json)","autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json)","autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json)","autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json)","autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json)","autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json)","autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json)","autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json)","autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json)","autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json)","autogpt_platform/backend/agents/calculator-agent.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/calculator-agent.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/calculator-agent.json)",".claude/skills/orchestrate/scripts/capacity.sh":"# Claude Directive: capacity.sh\n\nPath: `.claude/skills/orchestrate/scripts/capacity.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/capacity.sh)",".claude/skills/orchestrate/scripts/classify-pane.sh":"# Claude Directive: classify-pane.sh\n\nPath: `.claude/skills/orchestrate/scripts/classify-pane.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/classify-pane.sh)",".claude/skills/orchestrate/scripts/find-spare.sh":"# Claude Directive: find-spare.sh\n\nPath: `.claude/skills/orchestrate/scripts/find-spare.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/find-spare.sh)",".claude/skills/orchestrate/scripts/notify.sh":"# Claude Directive: notify.sh\n\nPath: `.claude/skills/orchestrate/scripts/notify.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/notify.sh)",".claude/skills/orchestrate/scripts/poll-cycle.sh":"# Claude Directive: poll-cycle.sh\n\nPath: `.claude/skills/orchestrate/scripts/poll-cycle.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/poll-cycle.sh)",".claude/skills/orchestrate/scripts/recycle-agent.sh":"# Claude Directive: recycle-agent.sh\n\nPath: `.claude/skills/orchestrate/scripts/recycle-agent.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/recycle-agent.sh)",".claude/skills/orchestrate/scripts/run-loop.sh":"# Claude Directive: run-loop.sh\n\nPath: `.claude/skills/orchestrate/scripts/run-loop.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/run-loop.sh)",".claude/skills/orchestrate/scripts/spawn-agent.sh":"# Claude Directive: spawn-agent.sh\n\nPath: `.claude/skills/orchestrate/scripts/spawn-agent.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/spawn-agent.sh)",".claude/skills/orchestrate/scripts/status.sh":"# Claude Directive: status.sh\n\nPath: `.claude/skills/orchestrate/scripts/status.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/status.sh)",".claude/skills/orchestrate/scripts/verify-complete.sh":"# Claude Directive: verify-complete.sh\n\nPath: `.claude/skills/orchestrate/scripts/verify-complete.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/verify-complete.sh)",".claude/skills/perf-compare/scripts/perf_compare.py":"# Claude Directive: perf_compare.py\n\nPath: `.claude/skills/perf-compare/scripts/perf_compare.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/perf_compare.py)",".claude/skills/perf-compare/scripts/test_perf_compare.py":"# Claude Directive: test_perf_compare.py\n\nPath: `.claude/skills/perf-compare/scripts/test_perf_compare.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/test_perf_compare.py)",".claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md)",".claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md)",".claude/skills/vercel-react-best-practices/rules/async-api-routes.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-api-routes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md)",".claude/skills/vercel-react-best-practices/rules/async-defer-await.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-defer-await.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md)",".claude/skills/vercel-react-best-practices/rules/async-dependencies.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md)",".claude/skills/vercel-react-best-practices/rules/async-parallel.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-parallel.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-parallel.md)",".claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md)",".claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md)",".claude/skills/vercel-react-best-practices/rules/bundle-conditional.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md)",".claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md)",".claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md)",".claude/skills/vercel-react-best-practices/rules/bundle-preload.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-preload.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md)",".claude/skills/vercel-react-best-practices/rules/client-event-listeners.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md)",".claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md)",".claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md)",".claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md)",".claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md)",".claude/skills/vercel-react-best-practices/rules/js-cache-storage.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md)",".claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md)",".claude/skills/vercel-react-best-practices/rules/js-early-exit.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-early-exit.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md)",".claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md)",".claude/skills/vercel-react-best-practices/rules/js-index-maps.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-index-maps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md)",".claude/skills/vercel-react-best-practices/rules/js-length-check-first.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md)",".claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md)",".claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md)",".claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md)",".claude/skills/vercel-react-best-practices/rules/rendering-activity.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-activity.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md)",".claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md)",".claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md)",".claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md)",".claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md)",".claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md)",".claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md)",".claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md)",".claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md)",".claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md)",".claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md)",".claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md)",".claude/skills/vercel-react-best-practices/rules/rerender-memo.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-memo.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md)",".claude/skills/vercel-react-best-practices/rules/rerender-transitions.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md)",".claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md)",".claude/skills/vercel-react-best-practices/rules/server-cache-lru.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md)",".claude/skills/vercel-react-best-practices/rules/server-cache-react.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-cache-react.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md)",".claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md)",".claude/skills/vercel-react-best-practices/rules/server-serialization.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-serialization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-serialization.md)","classic/original_autogpt/autogpt/agents/README.md":"# Subagent: readme\n\nPath: `classic/original_autogpt/autogpt/agents/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/original_autogpt/autogpt/agents/README.md)",".claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json)",".claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json)",".claude/skills/perf-compare/scripts/fixtures/error_400.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/error_400.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/error_400.json)",".claude/skills/perf-compare/scripts/fixtures/project_builder.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/project_builder.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/project_builder.json)",".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json)",".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json)",".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json)",".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json)","autogpt_platform/backend/backend/copilot/tools/IDEAS.md":"# Tools Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/IDEAS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/IDEAS.md)","autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json":"# Tools Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json)","autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json":"# Testdata Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json)","autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md":"# Webapp-testing Skill\n\nPath: `autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md)"},"files":{".claude/skills/perf-compare/SKILL.md":"---\nname: perf-compare\ndescription: \"Shared measurement recipe for AutoGPT performance work. Compares a baseline and a candidate in Sentry (two releases, two environments such as preview pr-<n> deploys, or two time windows) per transaction: count, p50/p95/p99, failure rate, and a trace-clustered bootstrap verdict of faster / slower / no detectable change / not enough data, with exit codes agents can branch on. Works for span duration, web vitals (measurements.lcp) or any numeric span field. TRIGGER when an agent needs to show a change made the backend or frontend faster or slower, compare a preview against a control or dev, check a release after merge on dev or prod, or check for regressions.\"\nuser-invocable: true\nargument-hint: \"[what to compare, e.g. 'builder release OLD vs NEW on prod' or 'pr-123 vs control pr-124 on /api/library/agents']\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# perf-compare: one way to measure performance\n\nEvery agent that tunes speed or stability measures with this recipe, so results are comparable across agents and noise is not reported as a win. Sentry is the store: org `significant-gravitas`, API `https://us.sentry.io`, tracing at 100%. `scripts/perf_compare.py` (Python 3.8+, standard library only) runs the queries and the statistics.\n\n```bash\nS=$(git rev-parse --show-toplevel)/.claude/skills/perf-compare/scripts/perf_compare.py\npython3 \"$S\" --help\n```\n\n## When to use it\n\n- Before claiming a change made something faster or slower, on a preview or after merge.\n- After merge, to check a win holds on dev and then prod (release against release).\n- To check a change did not regress other transactions (run without `--transaction`).\n- For any numeric field: `span.duration` (default), web vitals on builder pageloads (`measurements.lcp`), LLM token counts (`gen_ai.usage.input_tokens`), or custom attributes (`tags[name,number]`).\n\nIt is not for debugging one slow request (open the trace) or for error triage.\n\n## Token\n\nThe script reads `SENTRY_AUTH_TOKEN` from the environment and nowhere else. It needs a read-only token with exactly `org:read`, `project:read` and `event:read`: either a user auth token (Settings > Account > API > Auth Tokens) or an internal integration with Read on Organization, Project and Issue & Event. Organization auth tokens (`sntrys_...`) carry only `org:ci` and get HTTP 403 on these endpoints. A missing token exits with code 5 and these instructions. `--print-queries` needs no token.\n\n## Where things are in Sentry\n\n| Project | What it holds | Environments | `release` |\n|---|---|---|---|\n| `autogpt-server` | Backend: FastAPI routes (`/api/library/agents`, `/api/chat/sessions/{session_id}/stream`) and internal RPC services (`/update_node_execution_status`) | `app:prod-behave:cloud`, `app:dev-behave:cloud` | git sha once the backend sets it; check first |\n| `builder` | Frontend (Next.js on Vercel): `http.server` routes (`GET /api/proxy/[...path]`), `middleware GET`, `serverAction/*`, browser `pageload` and `navigation` with web vitals | `app:prod-behave:cloud` is prod; `app:preview-behave:cloud` is dev **and** every Vercel preview | git sha |\n| `autogpt-server-previews`, `builder-previews` | `!deploy` previews | `pr-<n>` | git sha |\n\nCheck a selector has data before you measure:\n\n```text\nsearch_events  projectSlug=<project>  dataset=spans  query=\"is_transaction:true\"\n               fields=[\"environment\",\"release\",\"count()\"]  sort=\"-count()\"  period=\"7d\"\n```\n\nIf `release` comes back null for a project, select by time window instead of release. If `autogpt-server-previews` has no `pr-<n>` events, that preview's backend is reporting into `autogpt-server` as `app:dev-behave:cloud`, mixed with dev traffic, and cannot be measured separately.\n\n## Choosing baseline and candidate\n\nBoth sides take `KEY=VALUE` pairs: `project=`, `env=`, `release=`, `query=` (extra Sentry filter), and time as `window=START/END` (repeatable), `start=`/`end=`, or `period=24h`. Shared defaults come from `--project`, `--env`, `--query` and `--period`/`--start`/`--end`/`--window`. A side's own `project=`, `env=` or `query=` wins over the shared value (`env=` with nothing after it clears it). Any time key on a side (`window=`, `start=`/`end=` or `period=`) replaces all of the shared time flags for that side. Times are UTC ISO 8601; without a time anywhere, both sides use the last 7 days.\n\n**1. After merge: release against release (dev or prod).** Same project and environment, previous release against new release, in windows of equal length that cover the same hours of the day: the 24 hours before the deploy against the 24 hours after, or 7 days against 7 days for quiet routes. Always filter by release when there is one. Old releases keep sending events for days (open tabs, Vercel skew protection), so a time window alone mixes releases.\n\n```bash\npython3 \"$S\" --project builder --env app:prod-behave:cloud \\\n  --baseline release=OLD_SHA window=2026-10-10T14:00:00Z/2026-10-11T14:00:00Z \\\n  --candidate release=NEW_SHA window=2026-10-11T14:00:00Z/2026-10-12T14:00:00Z\n```\n\n**2. On a preview: candidate preview against a control preview.** Deploy the change with `!deploy`, and deploy a control (the dev commit the change branches from, with no changes) the same way. Each gets its own namespace and an empty branch database, so the two are alike. Drive the same load at both, either together in one window or interleaved A B B A (control, candidate, candidate, control; about 10 minutes each) so drift cancels:\n\n```bash\npython3 \"$S\" --project autogpt-server-previews --transaction '/api/store/agents' \\\n  --baseline env=pr-CONTROL window=T0/T1 window=T3/T4 \\\n  --candidate env=pr-CANDIDATE window=T1/T2 window=T2/T3\n```\n\nMeasuring the candidate preview against `app:dev-behave:cloud` at dev HEAD in the same window is cheaper but weaker: dev has a full database, real traffic and different pods. Use it as a rough read, not as proof.\n\n**3. No release tag: before against after.** Equal windows either side of the deploy. Other deploys inside those windows also change things, so say so in your report.\n\n**4. A/A check before trusting a win.** Compare a selector with itself over two windows, for example the same release yesterday and today. It should say `no detectable change`. If it does not, your windows differ too much (hour of day, weekday, traffic mix), and so will your real comparison.\n\n## How much data\n\n- The script gives no p50 or p95 verdict unless each side has `--min-count` samples (default 200) **from** `--min-traces` distinct traces (default 30). At 200, p95 rests on 10 values above it. p99 is shown but never judged. The verdict cell says which minimum was missed. The failure-rate test works from Sentry's event counts instead, and needs `--min-count` events per side.\n- It samples up to `--sample-cap` values per side per transaction (default 1000, at most 20000), paging 100 at a time. A cap below `--min-count` is refused.\n- For a claim about one transaction, name it with `--transaction` and raise `--sample-cap` to 2000–5000. Naming it also shrinks the multiple-comparison correction (see below).\n- Correlated spans count once: the bootstrap resamples whole traces, and the trace minimum stops a few busy traces (a page polling an endpoint) from passing as a big sample. The table shows samples and distinct traces. The effective sample is closer to the trace count than the span count. In A/A tests on clustered data, resampling spans one at a time called 38–44% of identical pairs a change, against 2–5% when resampling by trace.\n- Drive at least 1000 requests per side per transaction you care about. Wait a few minutes after load stops before measuring, so Sentry has the last events.\n\n## Driving load on a preview\n\nA preview's backend is `https://pr-<N>-server.agpt.co` (REST under `/api`, health at `/health`), and its frontend is `https://autogpt-pr-<N>.vercel.app`.\n\nThe k6 tests are in `autogpt_platform/backend/load-tests/`; its `README.md` covers every test and setting. Run them on your own machine: `./k6.sh` runs k6 in Docker (`grafana/k6`), so nothing else needs installing, and a local run costs nothing beyond the load on the target. Do not run them on Grafana Cloud k6. It bills every virtual-user hour, so `./k6.sh` refuses cloud runs unless they are explicitly opted into, and the README reserves them for rare runs the team has agreed to. `K6_ENVIRONMENT=PREVIEW` with `PR_NUMBER=<N>` targets a preview:\n\n```bash\ncd autogpt_platform/backend/load-tests\nexport K6_ENVIRONMENT=PREVIEW PR_NUMBER=<N>\nVUS=5 DURATION=10m RAMP_UP=30s RAMP_DOWN=30s ./k6.sh run tests/api/core-api-test.js\n```\n\n- **Authentication needs no setup on a preview.** The authenticated tests (`tests/api/*`, `tests/basic/*`, `tests/marketplace/library-access-test.js`, `tests/comprehensive/platform-journey-test.js`) sign up throwaway `loadtest-*@previews.agpt.co` accounts in `setup()`, once per run, and use the Better Auth tokens the preview's frontend issues for them. `LOAD_TEST_ACCOUNT_COUNT` sets how many (default 5). `tests/marketplace/public-access-test.js` needs no account at all.\n- **To run as specific accounts**, such as the seeded `preview-*@previews.agpt.co` personas, set `LOAD_TEST_ACCOUNTS` to a JSON array of `{\"email\", \"password\"}`. The tests sign each one in once per run. Dev has no throwaway sign-up, so it always needs `LOAD_TEST_ACCOUNTS`.\n- **Note the run's UTC window** (`date -u +%FT%TZ` before and after) for the baseline and candidate windows above, and drive the same test, VUs and duration at both sides.\n- **Previews start with an empty database.** Store and library lists return nothing, so costs that grow with data (N+1 queries, scans) do not show. Seed data through the API first, or confirm on dev and prod after merge.\n- None of the k6 tests open copilot chats. To measure copilot turns, run chats against the preview yourself, or measure on dev and prod traffic.\n- The repository's evals (`backend/copilot/eval`, `backend/copilot/capabilities/eval`, `poetry run expert-style-eval`, `poetry run brain-dump-eval`) are offline quality and cost harnesses. They send no traffic to a deploy.\n- After merge, real traffic on dev and prod is the load.\n\n## Reading the verdict\n\nThe markdown output (default) or `--json` reports two things for each transaction.\n\n- **Sentry aggregates over every matching event:** count, p50, p95, p99 and failure rate. These are exact for the windows.\n- **A verdict for p50 and p95:**\n  - The interval is a bootstrap CI of candidate minus baseline from the raw samples, resampling whole traces.\n  - It is Bonferroni-adjusted so that every interval in the run holds together at `--confidence` (95%). Two transactions means four intervals at 98.75% each. The plain 95% interval is in the JSON as `ci`.\n  - Resamples are raised automatically until each tail of the adjusted interval holds 20 draws, up to 50000. For example, 4000 becomes 8000 for five transactions. At the 50000 cap a run still goes ahead with as few as 10 draws per tail. A run that would leave fewer than 10, because too many transactions are compared, is refused before it starts (exit 2). The JSON reports the number used as `settings.resamples`.\n  - `faster` or `slower` needs the adjusted interval to exclude zero, and **both** the sample change and Sentry's all-events change to be at least `--min-effect` (5%) in that direction. Anything else is `no detectable change`. Below `--min-count` samples or `--min-traces` traces it is `not enough data`.\n  - The transaction takes its worst percentile: `slower` if either is slower, else `faster` if either is faster.\n- **Failure rate:** Fisher's exact test on the failure counts behind Sentry's `failure_rate()`. It is exact because a normal approximation is wrong with a handful of failures: 0 of 1000 against 4 of 1000 passes a z-test, but Fisher gives p = 0.12.\n  - The significance level is Bonferroni-adjusted over the K transactions that have `--min-count` events on both sides. That is one test per transaction, not two.\n  - `higher` needs significance and a rise of at least `--min-failure-delta` (0.1 percentage points). It counts as a regression even when the transaction got faster, because requests that fail fast look like a speedup.\n  - The test treats every event as independent, and `--min-traces` does not apply to it. When failures come from a few traces (one broken page retrying), it overstates significance. Check `sample_traces` and the failing traces in Sentry before acting on a failure-rate result alone.\n\n`no detectable change` means \"not distinguishable from noise with this much data\". It does not mean \"no change\". Check the interval widths and trace counts before concluding anything.\n\n**A win counts** only when all of these hold:\n\n- you named the transaction with `--transaction` before measuring;\n- the verdict is `faster`;\n- the failure rate is not `higher`;\n- it holds again on a second, independent window or run.\n\nA run over the top transactions is for spotting regressions and leads, not for claiming wins.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | faster: a compared transaction got faster and nothing regressed |\n| 1 | slower or more failures: at least one compared transaction regressed |\n| 2 | usage error: bad arguments (including a `--region-url` that is not https, `--timeout` ≤ 0, or `--sample-cap` below `--min-count`), unknown project, or Sentry rejected the query (HTTP 400) |\n| 3 | no detectable change |\n| 4 | not enough data: no compared transaction met `--min-count` and `--min-traces` on both sides |\n| 5 | auth: `SENTRY_AUTH_TOKEN` missing, or Sentry rejected it (401/403) |\n| 6 | Sentry API or network error after retries, or any unexpected error; never read as a regression |\n\nWith `--json`, the fields to branch on are:\n\n- `overall.verdict`, `overall.exit_code` and `overall.reasons`;\n- per transaction in `transactions[]`:\n  - `verdict`, `note` (why there is not enough data), `regression`;\n  - `p50`/`p95`: `baseline`, `candidate`, `diff`, `rel`, `sentry_rel`, `ci`, `ci_adjusted`, `verdict`;\n  - `failure_rate`: `baseline`, `candidate`, `diff`, `p_value`, `verdict`;\n  - `baseline`/`candidate`: `count`, `p50`, `p95`, `p99`, `failure_rate`, `sample_n`, `sample_traces`;\n- `settings`, holding the values actually used: `sample_cap`, `resamples`, `comparisons`, `failure_comparisons`, `adjusted_confidence`, `min_count`, `min_traces`, `min_effect`, `seed`.\n\nErrors in `--json` mode print `{\"error\": {\"type\", \"message\"}, \"exit_code\"}`, where `type` is `usage`, `auth`, `api` or `internal`.\n\n## The exact queries\n\nAll requests are `GET https://us.sentry.io/api/0/organizations/significant-gravitas/events/` with `dataset=spans` and `project=<numeric id>`. The id comes from `GET /api/0/projects/significant-gravitas/<slug>/`. `start` and `end` (UTC) span the side's windows; the query's timestamp filter pins each window exactly. `F` is `--field`.\n\n1. **Aggregate per transaction**, one request per side:\n   - `field=transaction`, `count()`, `p50(F)`, `p95(F)`, `p99(F)`, `failure_rate()`\n   - `sort=-count()`, `per_page=100`\n2. **Raw sample**, per side per compared transaction:\n   - `field=F`, `trace`, `id`\n   - `sort=id`, `per_page=100`, paged with the `cursor` from the `Link` header (`rel=\"next\"; results=\"true\"`) until `--sample-cap` rows.\n   - Span ids are random, so the first N rows by `id` are a uniform random sample of the windows. Sorting by `-timestamp` instead returns the latest burst, often a few traces.\n\nBoth use this query string:\n\n```text\nis_transaction:true environment:\"ENV\" release:\"SHA\" [has:F] [(EXTRA QUERY)]\n  [transaction:\"T\" | (transaction:\"A\" OR transaction:\"B\")]\n  timestamp:>=2026-10-11T14:00:00Z timestamp:<2026-10-12T14:00:00Z\n```\n\nSeveral windows become `(timestamp:>=S1 timestamp:<E1 OR timestamp:>=S2 timestamp:<E2)`. `has:F` appears for any field other than `span.duration`.\n\n`python3 \"$S\" ... --print-queries` prints the exact REST URL and MCP arguments for an invocation, without a token.\n\n**With only the Sentry MCP**, use `search_events` with `dataset: \"spans\"`, or `search_traces` without `dataset`:\n\n- Pass `projectSlug`, the same `query`, `fields` and `sort`.\n- Pass a `period` long enough to cover the windows, such as `\"50h\"`; the timestamp filter does the exact cut. The argument is `period`, not `statsPeriod`, and an unknown key silently falls back to 14 days.\n- Pass `limit` ≤ 100.\n- Confirm the \"Executed Search\" block in the reply shows your query unchanged.\n- The MCP cannot page. For more than 100 samples, split each window into sub-windows, get `count()` for each, and take the first rows by `id` from each in proportion to its count.\n\n## Field names checked against live data\n\nChecked through the Sentry MCP on `builder` and `autogpt-server`.\n\n- **Filters:**\n  - `is_transaction:true` and `transaction:\"…\"`.\n  - `environment:\"…\"`: quote it, the values contain colons.\n  - `release:\"<sha>\"`.\n  - `span.op:` with `pageload`, `navigation`, `http.server`, `http.server.middleware` or `function.server_action`.\n  - `span.status:[…]` and `!span.status:[…]`.\n  - `has:measurements.lcp` and `has:tags[gen_ai.usage.input_tokens,number]`.\n  - `timestamp:>=…Z timestamp:<…Z` and parenthesised `OR` groups.\n- **Aggregates:**\n  - `count()`.\n  - `p50()`, `p75()`, `p95()` and `p99()`, of `span.duration` (ms), `measurements.lcp`, `measurements.fcp`, `measurements.ttfb`, `measurements.cls`, `gen_ai.usage.input_tokens` or `tags[gen_ai.usage.input_tokens,number]`.\n  - `failure_rate()`: the share of spans whose `span.status` is not `ok`, `cancelled` or `unknown`. It reconciles exactly with `count()` and `!span.status:[ok,cancelled,unknown]`.\n- **Sample fields:** `span.duration`, `trace`, `id`, `timestamp`, `release`. Sorting works by `id`, `-id` and `-timestamp`.\n- **`span.status` values seen:** `ok`, `internal_error`, `resource_exhausted`, `cancelled`, `deadline_exceeded`, `invalid_argument`, `not_found`, `already_exists`.\n- **Rejected:** wildcards on `id` such as `id:0*`; span ids must be full 16-character hex.\n\n## Pitfalls\n\n- **Environment names collide.** Builder's dev deploy and every Vercel preview share `app:preview-behave:cloud`. Tell them apart with `release`.\n- **Proxy routes measure the backend.** `GET /api/proxy/[...path]` mostly measures the backend behind it, not the frontend release, and a few polling pages contribute most of its spans.\n- **Middleware durations are quantized** to about 1 ms. A one-step move in p95 reads as +100%; look at the absolute change.\n- **Long-lived transactions measure session length.** `/ws` and `/api/chat/sessions/{session_id}/stream` last minutes to an hour, not request latency. Compare per-turn or per-node transactions instead.\n- **Weekday and hour of day move latency.** Saturday-to-Sunday against Sunday-to-Monday is not a fair pair for a release comparison. Use full days, the same hours, and the A/A check.\n- **Do not rerun until it says faster.** Repeating runs, or trying many transactions and reporting the best, turns noise into wins. Decide the transaction, windows and settings first, then run once and replicate once.\n\n## Tests\n\n```bash\npython3 -m unittest discover -s .claude/skills/perf-compare/scripts\n```\n\nThe tests run offline in a few seconds, against the synthetic Sentry responses in `scripts/fixtures/`, served by a fake transport or by a local `http.server`. They cover:\n\n- query building, time parsing and option validation;\n- cursor pagination, retries, and real-socket errors;\n- the percentile, bootstrap and Fisher maths with fixed seeds;\n- seeded A/A calibration on independent and trace-clustered data;\n- the verdict rules, every exit code, and the missing-token error.\n",".claude/skills/pr-review/SKILL.md":"---\nname: pr-review\ndescription: Review a PR for correctness, security, code quality, and testing issues. TRIGGER when user asks to review a PR, check PR quality, or give feedback on a PR.\nuser-invocable: true\nargs: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Review\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nBefore reading code, understand the **why**, **what**, and **how** from the PR description:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\nEvery PR should have a Why / What / How structure. If any of these are missing, note it as feedback.\n\n## Read the diff\n\n```bash\ngh pr diff {N}\n```\n\n## Fetch existing review comments\n\nBefore posting anything, fetch existing inline comments to avoid duplicates:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\n```\n\n## What to check\n\n**Description quality:** Does the PR description cover Why (motivation/problem), What (summary of changes), and How (approach/implementation details)? If any are missing, request them — you can't judge the approach without understanding the problem and intent.\n\n**Correctness:** logic errors, off-by-one, missing edge cases, race conditions (TOCTOU in file access, credit charging), error handling gaps, async correctness (missing `await`, unclosed resources).\n\n**Security:** input validation at boundaries, no injection (command, XSS, SQL), secrets not logged, file paths sanitized (`os.path.basename()` in error messages).\n\n**Code quality:** apply rules from backend/frontend CLAUDE.md files and root `AGENTS.md`, including its \"User-facing copy\" section (text a person reads says workflow, never agent or graph).\n\n**Architecture:** DRY, single responsibility, modular functions. `Security()` vs `Depends()` for FastAPI auth. `data:` for SSE events, `: comment` for heartbeats. `transaction=True` for Redis pipelines.\n\n**Testing:** edge cases covered, colocated `*_test.py` (backend) / `__tests__/` (frontend), mocks target where symbol is **used** not defined, `AsyncMock` for async.\n\n## Output format\n\nEvery comment **must** be prefixed with `🤖` and a criticality badge:\n\n| Tier | Badge | Meaning |\n|---|---|---|\n| Blocker | `🔴 **Blocker**` | Must fix before merge |\n| Should Fix | `🟠 **Should Fix**` | Important improvement |\n| Nice to Have | `🟡 **Nice to Have**` | Minor suggestion |\n| Nit | `🔵 **Nit**` | Style / wording |\n\nExample: `🤖 🔴 **Blocker**: Missing error handling for X — suggest wrapping in try/except.`\n\n## Post inline comments\n\nFor each finding, post an inline comment on the PR (do not just write a local report):\n\n```bash\n# Get the latest commit SHA for the PR\nCOMMIT_SHA=$(gh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.head.sha')\n\n# Post an inline comment on a specific file/line\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments \\\n  -f body=\"🤖 🔴 **Blocker**: <description>\" \\\n  -f commit_id=\"$COMMIT_SHA\" \\\n  -f path=\"<file path>\" \\\n  -F line=<line number>\n```\n",".claude/skills/open-pr/SKILL.md":"---\nname: open-pr\ndescription: Open a pull request with proper PR template, test coverage, and review workflow. Guides agents through creating a PR that follows repo conventions, ensures existing behaviors aren't broken, covers new behaviors with tests, and handles review via bot when local testing isn't possible. TRIGGER when user asks to \"open a PR\", \"create a PR\", \"make a PR\", \"submit a PR\", \"open pull request\", \"push and create PR\", or any variation of opening/submitting a pull request.\nuser-invocable: true\nargs: \"[base-branch] — optional target branch (defaults to dev).\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Open a Pull Request\n\n## Step 1: Pre-flight checks\n\nBefore opening the PR:\n\n1. Ensure all changes are committed\n2. Ensure the branch is pushed to the remote (`git push -u origin <branch>`)\n3. Run linters/formatters across the whole repo (not just changed files) and commit any fixes\n\n## Step 2: Test coverage\n\n**This is critical.** Before opening the PR, verify:\n\n### Existing behavior is not broken\n- Identify which modules/components your changes touch\n- Run the existing test suites for those areas\n- If tests fail, fix them before opening the PR — do not open a PR with known regressions\n\n### New behavior has test coverage\n- Every new feature, endpoint, or behavior change needs tests\n- If you added a new block, add tests for that block\n- If you changed API behavior, add or update API tests\n- If you changed frontend behavior, verify it doesn't break existing flows\n\nIf you cannot run the full test suite locally, note which tests you ran and which you couldn't in the test plan.\n\n## Step 3: Create the PR using the repo template\n\nRead the canonical PR template at `.github/PULL_REQUEST_TEMPLATE.md` and use it **verbatim** as your PR body:\n\n1. Read the template: `cat .github/PULL_REQUEST_TEMPLATE.md`\n2. Preserve the exact section titles and formatting, including:\n   - `### Why / What / How`\n   - `### Changes 🏗️`\n   - `### Checklist 📋`\n3. Replace HTML comment prompts (`<!-- ... -->`) with actual content; do not leave them in\n4. **Do not pre-check boxes** — leave all checkboxes as `- [ ]` until each step is actually completed\n5. Do not alter the template structure, rename sections, or remove any checklist items\n\n**PR title must use conventional commit format** (e.g., `feat(backend): add new block`, `fix(frontend): resolve routing bug`, `dx(skills): update PR workflow`). See CLAUDE.md for the full list of scopes.\n\nUse `gh pr create` with the base branch (defaults to `dev` if no `[base-branch]` was provided). Use `--body-file` to avoid shell interpretation of backticks and special characters:\n\n```bash\nBASE_BRANCH=\"${BASE_BRANCH:-dev}\"\nPR_BODY=$(mktemp)\ncat > \"$PR_BODY\" << 'PREOF'\n<filled-in template from .github/PULL_REQUEST_TEMPLATE.md>\nPREOF\ngh pr create --base \"$BASE_BRANCH\" --title \"<type>(scope): short description\" --body-file \"$PR_BODY\"\nrm \"$PR_BODY\"\n```\n\n## Step 4: Review workflow\n\n### If you have a workspace that allows testing (docker, running backend, etc.)\n- Run `/pr-test` to do E2E manual testing of the PR using docker compose, agent-browser, and API calls. This is the most thorough way to validate your changes before review.\n- After testing, run `/pr-review` to self-review the PR for correctness, security, code quality, and testing gaps before requesting human review.\n\n### If you do NOT have a workspace that allows testing\nThis is common for agents running in worktrees without a full stack. In this case:\n\n1. Run `/pr-review` locally to catch obvious issues before pushing\n2. **Comment `/review` on the PR** after creating it to trigger the review bot\n3. **Poll for the review** rather than blindly waiting — check for new review comments every 30 seconds using `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate` and the GraphQL inline threads query. The bot typically responds within 30 minutes, but polling lets the agent react as soon as it arrives.\n4. Do NOT proceed or merge until the bot review comes back — but bound the\n   wait at **60 minutes** from the `/review` comment, the same budget\n   `/pr-address` and `/pr-polish` use. If nothing has arrived by then, stop\n   waiting and tell the user the requested review never came. Never block\n   indefinitely on a bot.\n5. Address any issues the bot raises — use `/pr-address` which has a full polling loop with CI + comment tracking\n\n```bash\n# After creating the PR:\nPR_NUMBER=$(gh pr view --json number -q .number)\ngh pr comment \"$PR_NUMBER\" --body \"/review\"\n# Then use /pr-address to poll for and address the review when it arrives\n```\n\n## Step 5: Address review feedback\n\nOnce the review bot or human reviewers leave comments:\n- Run `/pr-address` to address review comments. It will loop until CI is green and all comments are resolved.\n- Do not merge without human approval.\n\n## Related skills\n\n| Skill | When to use |\n|---|---|\n| `/pr-test` | E2E testing with docker compose, agent-browser, API calls — use when you have a running workspace |\n| `/pr-review` | Review for correctness, security, code quality — use before requesting human review |\n| `/pr-address` | Address reviewer comments and loop until CI green — use after reviews come in; it can also post the `/review` trigger itself if the PR has no review yet |\n\n## Step 6: Post-creation\n\nAfter the PR is created and review is triggered:\n- Share the PR URL with the user\n- If waiting on the review bot, let the user know the expected wait time (~30 min)\n- Do not merge without human approval\n",".claude/skills/pr-polish/SKILL.md":"---\nname: pr-polish\ndescription: Alternate /pr-review and /pr-address on a PR until the PR is truly mergeable — no new review findings, zero unresolved inline threads, zero unaddressed top-level reviews or issue comments, all CI checks green, and two consecutive quiet polls after CI settles. Use when the user wants a PR polished to merge-ready without setting a fixed number of rounds.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Polish\n\n**Goal.** Drive a PR to merge-ready by alternating `/pr-review` and `/pr-address` until **all** of the following hold:\n\n1. The most recent `/pr-review` produces **zero new findings** (no new inline comments, no new top-level reviews with a non-empty body).\n2. Every inline review thread reachable via GraphQL reports `isResolved: true`.\n3. Every non-bot, non-author top-level review has been acknowledged (replied-to) OR resolved via a thread it spawned.\n4. Every non-bot, non-author issue comment has been acknowledged (replied-to).\n5. Every CI check is `conclusion: \"success\"` or `\"skipped\"` / `\"neutral\"` — none `\"failure\"` or still pending.\n6. **Two consecutive post-CI polls** (≥60s apart) stay clean — no new threads, no new non-empty reviews, no new issue comments. Bots (coderabbitai, sentry, autogpt-pr-reviewer) frequently post late after CI settles; a single green snapshot is not sufficient.\n7. **No requested-but-unanswered review.** If a `/review` comment was posted on the PR and the review bot has not replied yet, quiet polls do **not** count toward condition 6 — keep polling until the review lands or the wait budget expires. See [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n**Do not stop at a fixed number of rounds.** If round N introduces new comments, round N+1 is required. Cap at `_MAX_ROUNDS = 10` as a safety valve, but expect 2–5 in practice.\n\n## TodoWrite\n\nBefore starting, write two todos so the user can see the loop progression:\n\n- `Round {current}: /pr-review + /pr-address on PR #{N}` — current iteration.\n- `Final polish polling: 2 consecutive clean polls, CI green, 0 unresolved` — runs after the last non-empty review round.\n\nUpdate the `current` round counter at the start of each iteration; mark `completed` only when the round's address step finishes (all new threads addressed + resolved).\n\n## Find the PR\n\n```bash\nARG_PR=\"${ARG:-}\"\n# Normalize URL → numeric ID if the skill arg is a pull-request URL.\nif [[ \"$ARG_PR\" =~ ^https?://github\\.com/[^/]+/[^/]+/pull/([0-9]+) ]]; then\n  ARG_PR=\"${BASH_REMATCH[1]}\"\nfi\nPR=\"${ARG_PR:-$(gh pr list --head \"$(git branch --show-current)\" --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')}\"\nif [ -z \"$PR\" ] || [ \"$PR\" = \"null\" ]; then\n  echo \"No PR found for current branch. Provide a PR number or URL as the skill arg.\"\n  exit 1\nfi\necho \"Polishing PR #$PR\"\n```\n\n## The outer loop\n\n```text\nround = 0\nwhile round < _MAX_ROUNDS:\n    round += 1\n    baseline = snapshot_state(PR)   # see \"Snapshotting state\" below\n    invoke_skill(\"pr-review\", PR)   # posts findings as inline comments / top-level review\n    findings = diff_state(PR, baseline)\n    if findings.total == 0:\n        break  # no new findings → go to polish polling\n    invoke_skill(\"pr-address\", PR)  # resolves every unresolved thread + CI failure\n# Post-loop: polish polling (see below).\npolish_polling(PR)\n```\n\n### Optional: the review bot\n\n`invoke_skill(\"pr-review\", PR)` is the agent's **own** review and always runs every round. In addition to it, the repo's review bot can be summoned on demand by commenting `/review` on the PR (see the `open-pr` skill):\n\n```bash\ngh pr comment \"${PR}\" --body \"/review\"\n```\n\nThis is an **option, not a required step**, and it is **not** a substitute for the `/pr-review` round — use it when you want a second opinion the outer loop cannot produce itself, or when the PR carries no bot findings at all yet. Bot findings land as inline threads and top-level reviews, exactly like the agent's own findings, so both feed the same `invoke_skill(\"pr-address\", PR)` step — no separate handling is needed. If you do request one, the exit conditions must wait for it; see [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n### Snapshotting state\n\nBefore each `/pr-review`, capture a baseline so the diff after the review reflects **only** what the review just added (not pre-existing threads):\n\n```bash\n# Inline threads — total count + latest databaseId per thread\ngh api graphql -f query=\"\n{\n  repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n    pullRequest(number: ${PR}) {\n      reviewThreads(first: 100) {\n        totalCount\n        nodes {\n          id\n          isResolved\n          comments(last: 1) { nodes { databaseId } }\n        }\n      }\n    }\n  }\n}\" > /tmp/baseline_threads.json\n\n# Top-level reviews — count + latest id per non-empty review\ngh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate \\\n  --jq '[.[] | select((.body // \"\") != \"\") | {id, user: .user.login, state, submitted_at}]' \\\n  > /tmp/baseline_reviews.json\n\n# Issue comments — count + latest id per non-bot, non-author comment.\n# Bots are filtered by User.type == \"Bot\" (GitHub sets this for app/bot\n# accounts like coderabbitai, github-actions, sentry-io). The author is\n# filtered by comparing login to the PR author — export it so jq can see it.\nAUTHOR=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}\" --jq '.user.login')\n# Slash-command triggers are excluded here too: a `/review` posted by a\n# maintainer is non-bot and non-author, so without this it enters the baseline\n# and later registers as a \"new finding\", forcing an address round over a\n# comment with nothing to address.\ngh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq --arg author \"$AUTHOR\" \\\n      '[.[][] | select(.user.type != \"Bot\" and .user.login != $author)\n             | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)\n             | {id, user: .user.login, created_at}]' \\\n  > /tmp/baseline_issue_comments.json\n```\n\n### Diffing after a review\n\nAfter `/pr-review` runs, any of these counting as \"new findings\" means another address round is needed:\n\n- New inline thread `id` not in the baseline.\n- An existing thread whose latest comment `databaseId` is higher than the baseline's (new reply on an old thread).\n- A new top-level review `id` with a non-empty body.\n- A new issue comment `id` from a non-bot, non-author user.\n\nIf any of the four buckets is non-empty → not done; invoke `/pr-address` and loop.\n\n## Polish polling\n\nOnce `/pr-review` produces zero new findings, do **not** exit yet. Bots (coderabbitai, sentry, autogpt-pr-reviewer) commonly post late reviews after CI settles — 30–90 seconds after the final push. Poll at 60-second intervals:\n\n```text\nNON_SUCCESS_TERMINAL = {\"failure\", \"cancelled\", \"timed_out\", \"action_required\", \"startup_failure\"}\nclean_polls = 0\nrequired_clean = 2\nwhile clean_polls < required_clean:\n    # 1. CI gate — any terminal non-success conclusion (not just \"failure\")\n    # must trigger /pr-address. \"success\", \"skipped\", \"neutral\" are clean;\n    # anything else (including cancelled, timed_out, action_required) is a\n    # blocker that won't self-resolve.\n    ci = fetch_check_runs(PR)\n    if any ci.conclusion in NON_SUCCESS_TERMINAL:\n        invoke_skill(\"pr-address\", PR)  # address failures + any new comments\n        baseline = snapshot_state(PR)   # reset — push during address invalidates old baseline\n        clean_polls = 0\n        continue\n    if any ci.conclusion is None (still in_progress):\n        sleep 60; continue  # wait without counting this as clean\n\n    # 2. Comment / thread gate\n    threads = fetch_unresolved_threads(PR)\n    new_issue_comments = diff_against_baseline(issue_comments)\n    new_reviews = diff_against_baseline(reviews)\n    if threads or new_issue_comments or new_reviews:\n        invoke_skill(\"pr-address\", PR)\n        baseline = snapshot_state(PR)   # reset — the address loop just dealt with these,\n                                        # otherwise they stay \"new\" relative to the old baseline forever\n        clean_polls = 0\n        continue\n\n    # 3. Mergeability gate\n    mergeable = gh api repos/.../pulls/${PR} --jq '.mergeable'\n    if mergeable == false (CONFLICTING):\n        resolve_conflicts(PR)  # see pr-address skill\n        clean_polls = 0\n        continue\n    if mergeable is null (UNKNOWN):\n        sleep 60; continue\n\n    # 4. Pending-review gate — a /review with no bot answer yet.\n    # Quiet is exactly what a bot still thinking looks like, so don't bank\n    # a clean poll on it. review_requested_but_unanswered() already returns\n    # false once REVIEW_WAIT_BUDGET is spent, so the loop cannot hang here.\n    if review_requested_but_unanswered(PR):\n        sleep 60; continue\n\n    clean_polls += 1\n    sleep 60\n```\n\nOnly after `clean_polls == 2` do you report `ORCHESTRATOR:DONE`.\n\n### Concrete CI fetch (don't parse `gh pr checks` text columns)\n\nThe `fetch_check_runs(PR)` step above must use `--json`, not the default text output. Job names can contain spaces and parentheses (e.g. `test (3.11)`, `Analyze (python)`), so `gh pr checks $PR | awk '{print $2}'` extracts `(3.11)` instead of the status — leading to a clean-poll firing while jobs are still pending.\n\n```bash\n# Reliable: use --json so columns are unambiguous.\nci_json=$(gh pr checks $PR --repo Significant-Gravitas/AutoGPT --json name,state,bucket)\npending=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"pending\")] | length')\nfailed=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"fail\" or .bucket == \"cancel\")] | length')\n\n# Buckets are: pass | fail | pending | cancel | skipping\n# (NOTE: gh pr checks does NOT expose `conclusion` as a JSON field —\n# only `bucket`. Don't confuse with the GitHub REST API's check_runs\n# endpoint, which DOES use conclusion.)\n```\n\nMap back to the pseudocode above: `bucket == \"pending\"` is `ci.conclusion is None (still in_progress)`; `bucket in {\"fail\", \"cancel\"}` is `ci.conclusion in NON_SUCCESS_TERMINAL`; `bucket in {\"pass\", \"skipping\"}` is clean.\n\n### Waiting on a requested bot review\n\n`autogpt-pr-reviewer[bot]` typically takes ~30 minutes to answer a `/review` — far longer than the 60-second poll interval, so without this gate two clean polls would report `ORCHESTRATOR:DONE` while the requested review is still in flight.\n\n`review_requested_but_unanswered(PR)` is:\n\nRecompute from the API on **every** poll — never track elapsed time in a loop\nvariable, which is how this gate becomes an infinite wait. `--paginate`\nconcatenates one array per page, so `last` picks the last item of the final\npage rather than the newest overall; use `--slurp` and `max_by`. An answer is\n*either* a top-level review or an inline review comment, whichever is later —\nan inline-only reply is still an answer, and counting only top-level reviews\nleft the gate waiting after its threads had already been addressed.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\n`REVIEW_WAIT_BUDGET = 60 minutes`, measured as `now - REQUESTED_AT`:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\nAn observed reply on this repo took **46 minutes**, so a 45-minute budget\nwould have abandoned a review that was about to arrive. 60 gives headroom.\n\nPast the budget, stop waiting, resume counting clean polls normally, and note\nin the final report that the requested bot review never arrived. The loop must\nnever hang on a bot.\n\n**Never post a second `/review` while one is unanswered.** Check\n`review_requested_but_unanswered(PR)` before triggering, not just before\nexiting — otherwise each round requests another review and the bot answers a\nqueue of duplicates.\n\n### Why 2 clean polls, not 1\n\nA single green snapshot can be misleading — the final CI check often completes ~30s before a bot posts its delayed review. One quiet cycle does not prove the PR is stable; two consecutive cycles with no new threads, reviews, or issue comments arriving gives high confidence nothing else is incoming.\n\n### Why checking every source each poll\n\n`/pr-address` polling inside a single round already re-checks its own comments, but `/pr-polish` sits a level above and must also catch:\n\n- New top-level reviews (autogpt-pr-reviewer sometimes posts structured feedback only after several CI green cycles).\n- Issue comments from human reviewers (not caught by inline thread polling).\n- Sentry bug predictions that land on new line numbers post-push.\n- Merge conflicts introduced by a race between your push and a merge to `dev`.\n\n## Invocation pattern\n\nDelegate to existing skills with the `Skill` tool; do not re-implement the review or address logic inline. This keeps the polish loop focused on orchestration and lets the child skills evolve independently.\n\n```python\nSkill(skill=\"pr-review\",  args=pr_url)\nSkill(skill=\"pr-address\", args=pr_url)\n```\n\nAfter each child invocation, re-query GitHub state directly — never trust a summary for the stop condition. The orchestrator's `ORCHESTRATOR:DONE` is verified against actual GraphQL / REST responses per the rules in `pr-address`'s \"Verify actual count before outputting ORCHESTRATOR:DONE\" section.\n\n### **Auto-continue: do NOT end your response between child skills**\n\n`/pr-polish` is a single orchestration task — one invocation drives the PR all the way to merge-ready. When a child `Skill()` call returns control to you:\n\n- Do NOT summarize and stop.\n- Do NOT wait for user confirmation to continue.\n- Immediately, in the same response, perform the next loop step: state diff → decide next action → next `Skill()` call or polling sleep.\n\nThe child skill returning is a **loop iteration boundary**, not a conversation turn boundary. You are expected to keep going until one of the exit conditions in the opening section is met (2 consecutive clean polls, `_MAX_ROUNDS` hit, or an unrecoverable error).\n\nIf the user needs to approve a risky action mid-loop (e.g., a force-push or a destructive git operation), pause there — but not at the routine \"round N finished, round N+1 needed\" boundary. Those are silent transitions.\n\n### **Run /pr-polish in the foreground — never in a background agent**\n\nSpawning `/pr-polish` inside an `Agent(subagent_type=\"general-purpose\")` background task **does not work**. Background agents don't inherit the parent's slash-command registry, so `Skill(skill=\"pr-review\")` and `Skill(skill=\"pr-address\")` calls aren't available — the agent has to manually replicate the child skills' logic, which is fragile and tends to stall on the first network or rate-limit hiccup. Symptom: the background task reports `stalled: no progress for 600s` mid-review.\n\nRun `/pr-polish` inline in the foreground conversation. If the user asks for \"/pr-polish + /pr-test in parallel\", split them: foreground `/pr-polish`, and ONLY then can the test step go to a background agent (because `/pr-test` doesn't itself need to invoke skills).\n\n### **You MUST invoke `Skill(pr-review)` every round — even when bot reviews already exist**\n\nA common failure mode: CodeRabbit / autogpt-pr-reviewer / Sentry have already posted findings on the PR, and the orchestrator skips the `Skill(pr-review)` step on the assumption that \"review has been done.\" That's wrong — the outer loop's purpose is to layer **the agent's own review** on top of the bot reviews, catching issues the bots miss (architecture, naming, cross-file invariants, hidden coupling). If the orchestrator only addresses bot findings without ever running its own review, the loop converges to \"bot-clean\" but not \"agent-reviewed-clean,\" and the user reasonably asks \"did /pr-polish even read the diff?\"\n\n**Self-check before reporting `ORCHESTRATOR:DONE`:** confirm at least one `Skill(skill=\"pr-review\")` call appears in the current orchestration. If none, the loop is incomplete — go back and run one round.\n\n## GitHub rate limits\n\nThis skill issues many GraphQL calls (one review-thread query per outer iteration plus per-poll queries inside polish polling). Expect the GraphQL budget to be tight on large PRs. When `gh api rate_limit --jq .resources.graphql.remaining` drops below ~200, back off:\n\n- Fall back to REST for reads (flat `/pulls/{N}/comments`, `/pulls/{N}/reviews`, `/issues/{N}/comments`) per the `pr-address` skill's GraphQL-fallback section.\n- Queue thread resolutions (GraphQL-only) until the budget resets; keep making progress on fixes + REST replies meanwhile.\n- `sleep 5` between any batch of ≥20 writes to avoid secondary rate limits.\n\n## Safety valves\n\n- `_MAX_ROUNDS = 10` — if review+address rounds exceed this, stop and escalate to the user with a summary of what's still unresolved. A PR that cannot converge in 10 rounds has systemic issues that need human judgment.\n- After each commit, run `poetry run format` / `pnpm format && pnpm lint && pnpm types` per the target codebase's conventions. A failing format check is CI `failure` that will never self-resolve.\n- Every `/pr-review` round checks for **duplicate** concerns first (via `pr-review`'s own \"Fetch existing review comments\" step) so the loop does not re-post the same finding that a prior round already resolved.\n\n## Reporting\n\nWhen the skill finishes (either via two clean polls or hitting `_MAX_ROUNDS`), produce a compact summary:\n\n```\nPR #{N} polish complete ({rounds_completed} rounds):\n- {X} inline threads opened and resolved\n- {Y} CI failures fixed\n- {Z} new commits pushed\nFinal state: CI green, {total} threads all resolved, mergeable.\n```\n\nIf exiting via `_MAX_ROUNDS`, flag explicitly:\n\n```\nPR #{N} polish stopped at {_MAX_ROUNDS} rounds — NOT merge-ready:\n- {N} threads still unresolved: {titles}\n- CI status: {summary}\nNeeds human review.\n```\n\n## When to use this skill\n\nUse when the user says any of:\n- \"polish this PR\"\n- \"keep reviewing and addressing until it's mergeable\"\n- \"loop /pr-review + /pr-address until done\"\n- \"make sure the PR is actually merge-ready\"\n\nDo **not** use when:\n- User wants just one review pass (→ `/pr-review`).\n- User wants to address already-posted comments without further self-review (→ `/pr-address`).\n- A fixed round count is explicitly requested (e.g., \"do 3 rounds\") — honour the count instead of converging.\n",".claude/skills/pr-address/SKILL.md":"---\nname: pr-address\ndescription: Address PR review comments and loop until CI green and all comments resolved. TRIGGER when user asks to address comments, fix PR feedback, respond to reviewers, or babysit/monitor a PR.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Address\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nUnderstand the **Why / What / How** before addressing comments — you need context to make good fixes:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\n> If GraphQL is rate-limited, `gh pr view` fails. See [GitHub rate limits](#github-rate-limits) for REST fallbacks.\n\n## Optional: trigger the review bot\n\n**Only relevant if the PR has no review yet.** If the fetch steps below turn up no reviews and no inline threads, there is nothing to address *yet* — that is not the same as being done. You can summon the repo's review bot yourself:\n\n```bash\ngh pr comment {N} --body \"/review\"\n```\n\nThis is an **option, not a required step**. Skip it whenever the PR already has reviews (bot or human) — those are what you are here to address. See the `open-pr` skill's \"Review workflow\" step for the same trigger in the PR-creation flow.\n\n`autogpt-pr-reviewer[bot]` typically responds within ~30 minutes. **Poll for it rather than sleeping** — the loop in [Polling for CI + new comments](#polling-for-ci--new-comments) already re-checks every source every 30 seconds, so run that loop and react the moment the review lands. Note the `id` and `created_at` of the `/review` comment you posted; [Waiting on a requested review](#waiting-on-a-requested-review) needs them for the exit condition.\n\n## Fetch comments (all sources)\n\n### 1. Inline review threads — GraphQL (primary source of actionable items)\n\n> ⚠️ **WARNING — PAGINATE ALL PAGES BEFORE ADDRESSING ANYTHING**\n>\n> `reviewThreads(first: 100)` returns at most 100 threads per page AND returns threads **oldest-first**. On a PR with many review cycles (e.g. 373 threads), the oldest 100–200 threads are from past cycles and are **all already resolved**. Filtering client-side with `select(.isResolved == false)` on page 1 therefore yields **0 results** — even though pages 2–4 contain many unresolved threads from recent review cycles.\n>\n> **This is the most common failure mode:** agent fetches page 1, sees 0 unresolved after filtering, stops pagination, reports \"done\" — while hundreds of unresolved threads sit on later pages.\n>\n> One observed PR had 142 total threads: page 1 returned 0 unresolved (all old/resolved), while pages 2–3 had 111 unresolved. Another with 373 threads across 4 pages also had page 1 entirely resolved.\n>\n> **The rule: ALWAYS paginate to `hasNextPage == false` regardless of the per-page unresolved count. Never stop early because a page returns 0 unresolved.**\n\n**Step 1 — Fetch total count and sanity-check the newest threads:**\n\n```bash\n# Get total count and the newest 100 threads (last: 100 returns newest-first)\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n      newest: reviewThreads(last: 100) {\n        nodes { isResolved }\n      }\n    }\n  }\n}' | jq '{ total: .data.repository.pullRequest.reviewThreads.totalCount, newest_unresolved: [.data.repository.pullRequest.newest.nodes[] | select(.isResolved == false)] | length }'\n```\n\nIf `total > 100`, you have multiple pages — you **must** paginate all of them regardless of what `newest_unresolved` shows. The `last: 100` check is a sanity signal only; the full loop below is mandatory.\n\n**Step 2 — Collect all unresolved thread IDs across all pages:**\n\n```bash\n# Accumulate all unresolved threads — loop until hasNextPage == false\nCURSOR=\"\"\nALL_THREADS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            id\n            isResolved\n            path\n            line\n            comments(last: 1) {\n              nodes { databaseId body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  # Append unresolved nodes from this page\n  PAGE_THREADS=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)]')\n  ALL_THREADS=$(echo \"$ALL_THREADS $PAGE_THREADS\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\n\n# Reverse so newest threads (last pages) are addressed first — GitHub returns oldest-first\n# and the most recent review cycle's comments are the ones blocking approval.\nALL_THREADS=$(echo \"$ALL_THREADS\" | jq 'reverse')\n\necho \"Total unresolved threads: $(echo \"$ALL_THREADS\" | jq 'length')\"\necho \"$ALL_THREADS\" | jq '[.[] | {id, path, line, body: .comments.nodes[0].body[:200]}]'\n```\n\n**Step 3 — Address every thread in `ALL_THREADS`, then resolve.**\n\nOnly after this loop completes (all pages fetched, count confirmed) should you begin making fixes.\n\n> **Why reverse?** GraphQL returns threads oldest-first and exposes no `orderBy` option. A PR with 373 threads has ~4 pages; threads from the latest review cycle land on the last pages. Processing in reverse ensures the newest, most blocking comments are addressed first — the earlier pages mostly contain outdated threads from prior cycles.\n\n**Filter to unresolved threads only** — skip any thread where `isResolved: true`. `comments(last: 1)` returns the most recent comment in the thread — act on that; it reflects the reviewer's final ask. Use the thread `id` (Relay global ID) to track threads across polls.\n\n> If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the REST fallback (flat comment list — no thread grouping or `isResolved`).\n\n### 2. Top-level reviews — REST (MUST paginate)\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits or outages. Continue polling reviews normally even when GraphQL is exhausted.**\n\n**CRITICAL — always `--paginate`.** Reviews default to 30 per page. PRs can have 80–170+ reviews (mostly empty resolution events). Without pagination you miss reviews past position 30 — including `autogpt-pr-reviewer[bot]`'s structured review which is typically posted after several CI runs and sits well beyond the first page.\n\nTwo things to extract:\n- **Overall state**: look for `CHANGES_REQUESTED` or `APPROVED` reviews.\n- **Actionable feedback**: non-empty bodies only. Empty-body reviews are thread-resolution events — they indicate progress but have no feedback to act on.\n\n**Where each reviewer posts:**\n- `autogpt-pr-reviewer[bot]` — posts detailed structured reviews (\"Blockers\", \"Should Fix\", \"Nice to Have\") as **top-level reviews**. Not present on every PR. Address ALL items.\n- `sentry[bot]` — posts bug predictions as **inline threads**. Fix real bugs, explain false positives.\n- `coderabbitai[bot]` — posts summaries as **top-level reviews** AND actionable items as **inline threads**. Address actionable items.\n- Human reviewers — can post in any source. Address ALL non-empty feedback.\n\n### 3. PR conversation comments — REST\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits.**\n\nMostly contains: bot summaries (`coderabbitai[bot]`), CI/conflict detection (`github-actions[bot]`), and author status updates. Scan for non-empty messages from non-bot human reviewers that aren't the PR author — those are the ones that need a response.\n\n**Slash-command trigger comments are never actionable items — skip them.** A comment whose entire body is a slash command (`/review` being the common one) exists to summon the review bot; it is not a request directed at you. Do not reply to it, do not treat it as feedback, and do not count it as a \"new comment\" that restarts the loop. The non-author filter above happens to hide the usual case where the PR author posted it, but that is incidental — a maintainer can post `/review` on someone else's PR, and it must still be skipped. The actionable content is the review it produces, which arrives as a top-level review and/or inline threads, not as a conversation comment.\n\n## For each unaddressed comment\n\n**CRITICAL: The only valid sequence is fix → commit → push → reply → resolve. Never resolve a thread without a real code commit.**\n\nResolving a thread via `resolveReviewThread` without an actual fix is the most common failure mode — it makes unresolved counts drop without any real change, producing a false \"done\" signal. If the issue was genuinely a false positive (no code change needed), reply explaining why and then resolve. Otherwise:\n\nAddress comments **one at a time**: fix → commit → push → inline reply → resolve.\n\n1. Read the referenced code, make the fix (or reply explaining why it's not needed)\n2. Commit and push the fix\n3. Reply **inline** (not as a new top-level comment) referencing the fixing commit — this is what resolves the conversation for bot reviewers (coderabbitai, sentry):\n\nUse a **markdown commit link** so GitHub renders it as a clickable reference. Always get the full SHA with `git rev-parse HEAD` **after** committing — never copy a SHA from a previous commit or hardcode one:\n\n```bash\nFULL_SHA=$(git rev-parse HEAD)\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies \\\n  -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): <description>\"\n```\n\n| Comment type | How to reply |\n|---|---|\n| Inline review (`pulls/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n| Conversation (`issues/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n\n### What counts as a valid resolution\n\nOnly two situations justify calling `resolveReviewThread`:\n\n1. **Real code fix**: you changed the code, committed + pushed, and replied with the SHA. The commit diff must actually address the concern — not just touch the same file.\n2. **Genuine false positive**: the reviewer's concern does not apply to this code, and you can give a specific technical reason (e.g. \"Not applicable — `sdk_cwd` is pre-validated by `_make_sdk_cwd()` which applies normpath + prefix assertion before reaching this point\").\n\n**Anti-patterns that look resolved but aren't — never do these:**\n- `\"Accepted, tracked as follow-up\"` — a deferral, not a fix. The concern is still open. Do not resolve.\n- `\"Acknowledged\"` or `\"Same as above\"` — these are acknowledgements, not fixes. Do not resolve.\n- `\"Fixed in abc1234\"` where `abc1234` is a commit that doesn't actually change the flagged line/logic — dishonest. Verify `git show abc1234 -- path/to/file` changes the right thing before posting.\n- Resolving without replying — the reviewer never sees what happened.\n\nWhen in doubt: if a code change is needed, make it. A deferred issue means the thread stays open until the follow-up PR is merged.\n\n## Codecov coverage\n\nCodecov patch target is **80%** on changed lines. Checks are **informational** (not blocking) but should be green.\n\n### Running coverage locally\n\n**Backend** (from `autogpt_platform/backend/`):\n```bash\npoetry run pytest -s -vv --cov=backend --cov-branch --cov-report term-missing\n```\n\n**Frontend** (from `autogpt_platform/frontend/`):\n```bash\npnpm vitest run --coverage\n```\n\n### When codecov/patch fails\n\n1. Find uncovered files: `git diff --name-only $(gh pr view --json baseRefName --jq '.baseRefName')...HEAD`\n2. For each uncovered file — extract inline logic to `helpers.ts`/`helpers.py` and test those (highest ROI). Colocate tests as `*_test.py` (backend) or `__tests__/*.test.ts` (frontend).\n3. Run coverage locally to verify, commit, push.\n\n## Format and commit\n\nAfter fixing, format the changed code:\n\n- **Backend** (from `autogpt_platform/backend/`): `poetry run format`\n- **Frontend** (from `autogpt_platform/frontend/`): `pnpm format && pnpm lint && pnpm types`\n\nIf API routes changed, regenerate the frontend client:\n```bash\ncd autogpt_platform/backend && poetry run rest &\nREST_PID=$!\ntrap \"kill $REST_PID 2>/dev/null\" EXIT\nWAIT=0; until curl -sf http://localhost:8006/health > /dev/null 2>&1; do sleep 1; WAIT=$((WAIT+1)); [ $WAIT -ge 60 ] && echo \"Timed out\" && exit 1; done\ncd ../frontend && pnpm generate:api:force\nkill $REST_PID 2>/dev/null; trap - EXIT\n```\nNever manually edit files in `src/app/api/__generated__/`.\n\nThen commit and **push immediately** — never batch commits without pushing. Each fix should be visible on GitHub right away so CI can start and reviewers can see progress.\n\n**Never push empty commits** (`git commit --allow-empty`) to re-trigger CI or bot checks. When a check fails, investigate the root cause (unchecked PR checklist, unaddressed review comments, code issues) and fix those directly. Empty commits add noise to git history.\n\nFor backend commits in worktrees: `poetry run git commit` (pre-commit hooks).\n\n## Coverage\n\nCodecov enforces patch coverage on new/changed lines — new code you write must be tested. Before pushing, verify you haven't left new lines uncovered:\n\n```bash\ncd autogpt_platform/backend\npoetry run pytest --cov=. --cov-report=term-missing {path/to/changed/module}\n```\n\nLook for lines marked `miss` — those are uncovered. Add tests for any new code you wrote as part of addressing comments.\n\n**Rules:**\n- New code you add should have tests\n- Don't remove existing tests when fixing comments\n- If a reviewer asks you to delete code, also delete its tests, but verify coverage hasn't dropped on remaining lines\n\n## The loop\n\n```text\naddress comments → format → commit → push\n→ wait for CI (while addressing new comments) → fix failures → push\n→ re-check comments after CI settles\n→ repeat until: all comments addressed AND CI green AND no new comments arriving\n```\n\n### Polling for CI + new comments\n\nAfter pushing, poll for **both** CI status and new comments in a single loop. Do not use `gh pr checks --watch` — it blocks the tool and prevents reacting to new comments while CI is running.\n\n> **Note:** `gh pr checks --watch --fail-fast` is tempting but it blocks the entire Bash tool call, meaning the agent cannot check for or address new comments until CI fully completes. Always poll manually instead.\n\n**Polling loop — repeat every 30 seconds:**\n\n1. Check CI status:\n```bash\ngh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,name,link\n```\n   Parse the results: if every check has `bucket` of `\"pass\"` or `\"skipping\"`, CI is green. If any has `\"fail\"`, CI has failed. Otherwise CI is still pending.\n\n2. Check for merge conflicts:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json mergeable --jq '.mergeable'\n```\n   If the result is `\"CONFLICTING\"`, the PR has a merge conflict — see \"Resolving merge conflicts\" below. If `\"UNKNOWN\"`, GitHub is still computing mergeability — wait and re-check next poll.\n\n3. Check for new/changed comments (all three sources):\n\n   **Inline threads** — re-run the GraphQL query from \"Fetch comments\". For each unresolved thread, record `{thread_id, last_comment_databaseId}` as your baseline. On each poll, action is needed if:\n   - A new thread `id` appears that wasn't in the baseline (new thread), OR\n   - An existing thread's `last_comment_databaseId` has changed (new reply on existing thread)\n\n   **Conversation comments:**\n   ```bash\n   # Exclude slash-command triggers here, not just when reading — otherwise a\n   # `/review` (yours or a maintainer's) counts as a \"new comment\" and forces\n   # an address round over a comment with nothing to address.\n   gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate --slurp \\\n     | jq '[.[][] | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)]'\n   ```\n   Compare total count and newest `id` against baseline. Filter to non-empty, non-bot, non-author-update messages.\n\n   **Top-level reviews:**\n   ```bash\n   gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n   ```\n   Watch for new non-empty reviews (`CHANGES_REQUESTED` or `COMMENTED` with body). Compare total count and newest `id` against baseline.\n\n4. **React in this precedence order (first match wins):**\n\n| What happened | Action |\n|---|---|\n| Merge conflict detected | See \"Resolving merge conflicts\" below. |\n| Mergeability is `UNKNOWN` | GitHub is still computing mergeability. Sleep 30 seconds, then restart polling from the top. |\n| New comments detected | Address them (fix → commit → push → reply). After pushing, re-fetch all comments to update your baseline, then restart this polling loop from the top (new commits invalidate CI status). |\n| CI failed (bucket == \"fail\") | Get failed check links: `gh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,link --jq '.[] \\| select(.bucket == \"fail\") \\| .link'`. Extract run ID from link (format: `.../actions/runs/<run-id>/job/...`), read logs with `gh run view <run-id> --repo Significant-Gravitas/AutoGPT --log-failed`. Fix → commit → push → restart polling. |\n| CI green + no new comments, but a requested `/review` is unanswered | The bot is still working — quiet polls are exactly what that looks like. Keep polling; do **not** count these as the quiet polls below. See \"Waiting on a requested review\". |\n| CI green + no new comments | **Do not exit immediately.** Bots (coderabbitai, sentry) often post reviews shortly after CI settles. Continue polling for **2 more cycles (60s)** after CI goes green. Only exit after 2 consecutive green+quiet polls. |\n| CI pending + no new comments | Sleep 30 seconds, then poll again. |\n\n**The loop ends when:** CI fully green + all comments addressed + **2 consecutive polls with no new comments after CI settled** + no requested-but-unanswered `/review` (see below).\n\n### Waiting on a requested review\n\nIf a `/review` comment was posted on this PR and the bot has not answered it yet, the \"2 consecutive green+quiet polls\" exit is **premature** — a pending bot review looks identical to a quiet PR. Keep polling until one of these is true:\n\n- **The review arrived.** An answer is *either* a top-level review or an inline review comment from `autogpt-pr-reviewer[bot]`, whichever is later — an inline-only reply is still an answer, and counting only top-level reviews leaves the gate waiting after its threads have already been addressed. Once it lands, treat it as \"New comments detected\" and address it normally.\n- **The budget ran out.** Stop waiting **60 minutes** after the `/review` comment's `created_at`. Then exit per the normal rule and tell the user the requested review never arrived. Never hang indefinitely on a bot.\n\nRecompute both timestamps from the API on every poll rather than tracking\nelapsed time in a loop variable — a counter that some branch forgets to\nincrement is how this gate becomes an infinite wait.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\nA `/review` posted before this session still counts. Budget is **60 minutes**\nfrom `REQUESTED_AT`: an observed reply on this repo took 46 minutes, so 45\nwould have abandoned a review that was about to land. Enforce it in the same\npoll so the loop cannot wait forever:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\n**Do not post another `/review` while one is unanswered** — check this same\ncondition before triggering, or each round queues a duplicate request.\n\n### Resolving merge conflicts\n\n1. Identify the PR's target branch and remote:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json baseRefName --jq '.baseRefName'\ngit remote -v   # find the remote pointing to Significant-Gravitas/AutoGPT (typically 'upstream' in forks, 'origin' for direct contributors)\n```\n\n2. Pull the latest base branch with a 3-way merge:\n```bash\ngit pull {base-remote} {base-branch} --no-rebase\n```\n\n3. Resolve conflicting files, then verify no conflict markers remain:\n```bash\nif grep -R -n -E '^(<<<<<<<|=======|>>>>>>>)' <conflicted-files>; then\n  echo \"Unresolved conflict markers found — resolve before proceeding.\"\n  exit 1\nfi\n```\n\n4. Stage and push:\n```bash\ngit add <conflicted-files>\ngit commit -m \"Resolve merge conflicts with {base-branch}\"\ngit push\n```\n\n5. Restart the polling loop from the top — new commits reset CI status.\n\n## GitHub rate limits\n\nThree distinct rate limits exist — they have different causes, error shapes, and recovery times:\n\n| Error | HTTP code | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `{\"message\":\"API rate limit exceeded\"}` | 429 | Primary REST rate limit — 5000 calls/hr per user | Wait until `X-RateLimit-Reset` header timestamp |\n| `GraphQL: API rate limit already exceeded for user ID ...` | 403 on stderr, `gh` exits 1 | **GraphQL-specific** per-user limit — distinct from REST's 5000/hr and from the abuse secondary limit. Trips faster than REST because point costs per query. | Wait until the GraphQL window resets (typically ~1 hour from the first call in the window). REST still works — use fallbacks below. |\n\n**Prevention:** Add `sleep 3` between individual thread reply API calls. When posting >20 replies, increase to `sleep 5`.\n\n### Detection\n\nThe `gh` CLI surfaces the GraphQL limit on stderr with the exact string `GraphQL: API rate limit already exceeded for user ID <id>` and exits 1 — any `gh api graphql ...` **or** `gh pr view ...` call fails. Check current quota and reset time via the REST endpoint that reports GraphQL quota (this call is REST and still works whether GraphQL is rate-limited OR fully down):\n\n```bash\ngh api rate_limit --jq '.resources.graphql'   # { \"limit\": 5000, \"used\": 5000, \"remaining\": 0, \"reset\": 1729...}\n# Human-readable reset:\ngh api rate_limit --jq '.resources.graphql.reset' | xargs -I{} date -r {}\n```\n\nRetry when `remaining > 0`. If you need to proceed sooner, sleep 2–5 min and probe again — the limit is per user, not per machine, so other concurrent agents under the same token also consume it.\n\n### What keeps working\n\nWhen GraphQL is unavailable (rate-limited or outage):\n\n- **Keeps working (REST):** top-level reviews fetch, conversation comments fetch, all inline-comment replies, CI status (`gh pr checks`), and the `gh api rate_limit` probe.\n- **Degraded:** inline thread list — fall back to flat `/pulls/{N}/comments` REST, which drops thread grouping, `isResolved`, and Relay thread IDs. You still get comment bodies and the `databaseId` as `id`, enough to read and reply.\n- **Blocked:** `gh pr view`, the `resolveReviewThread` mutation, and any new `gh api graphql` queries — wait for the quota to reset.\n\n### Fall back to REST\n\n**PR metadata reads** — `gh pr view` uses GraphQL under the hood; use the REST pulls endpoint instead, which returns the full PR object:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.body'           # == --json body\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.base.ref'       # == --json baseRefName\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.mergeable'      # == --json mergeable\n```\n\nNote: REST `mergeable` returns `true|false|null`; GraphQL returns `MERGEABLE|CONFLICTING|UNKNOWN`. The `null` case maps to `UNKNOWN` — treat it the same (still computing; poll again).\n\n**Inline comments (flat list)** — no thread grouping or `isResolved`, but enough to read and reply:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate \\\n  | jq '[.[] | {id, path, line, user: .user.login, body: .body[:200], in_reply_to_id}]'\n```\n\nUse this degraded mode to make progress on the fix → reply loop, then return to GraphQL for `resolveReviewThread` once the rate limit resets.\n\n**Replies** — already REST-native (`/pulls/{N}/comments/{ID}/replies`); no change needed, use the same command as the main flow.\n\n**`resolveReviewThread`** — **no REST equivalent**; GitHub does not expose a REST endpoint for thread resolution. Queue the thread IDs needing resolution, wait for the GraphQL limit to reset, then run the resolve mutations in a batch (with `sleep 3` between calls, per the secondary-limit guidance).\n\n### Recovery from secondary rate limit (403 abuse)\n\n1. Stop all API writes immediately\n2. Wait **2 minutes minimum** (not 60s — secondary limits are stricter)\n3. Resume with `sleep 3` between each call\n4. If 403 persists after 2 min, wait another 2 min before retrying\n\nNever batch all replies in a tight loop — always space them out.\n\n## Parallel thread resolution\n\nWhen a PR has more than 10 unresolved threads, addressing one commit per thread is slow. Use this strategy instead:\n\n### Group by file, batch per commit\n\n1. Sort `ALL_THREADS` by `path` — threads in the same file can share a single commit.\n2. Fix all threads in one file → `git commit` → `git push` → reply to **all** those threads with the same SHA → resolve them all.\n3. Move to the next file group and repeat.\n\nThis reduces N commits to (number of files touched), which is usually 3–5 instead of 15–30.\n\n### Posting replies concurrently (for large batches)\n\nFor truly independent thread groups (different files, no shared logic), you can post replies in parallel using background subshells — but always space out API writes:\n\n```bash\n# Post replies to a batch of threads concurrently, 3s apart\n(\n  sleep 3\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID1}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\n(\n  sleep 6\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID2}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\nwait  # wait for all background replies before resolving\n```\n\nThen resolve sequentially (GraphQL mutations):\n```bash\nfor THREAD_ID in \"$THREAD1\" \"$THREAD2\" \"$THREAD3\"; do\n  gh api graphql -f query=\"mutation { resolveReviewThread(input: {threadId: \\\"${THREAD_ID}\\\"}) { thread { isResolved } } }\"\n  sleep 3\ndone\n```\n\n**Always sleep 3s between individual API writes** — GitHub's secondary rate limit (403) triggers on bursts of >20 writes. Increase to `sleep 5` when posting more than 20 replies in a batch.\n\n## Resolving threads via GraphQL\n\nUse `resolveReviewThread` **only after** the commit is pushed and the reply is posted:\n\n```bash\ngh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"THREAD_ID\"}) { thread { isResolved } } }'\n```\n\n**Never call this mutation before committing the fix.** The orchestrator will verify actual unresolved counts via GraphQL after you output `ORCHESTRATOR:DONE` — false resolutions will be caught and you will be re-briefed.\n\n> `resolveReviewThread` is GraphQL-only — no REST equivalent. If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the queue-and-retry flow.\n\n### Verify actual count before outputting ORCHESTRATOR:DONE\n\nBefore claiming \"0 unresolved threads\", always query GitHub directly — don't rely on your own bookkeeping. Paginate all pages — a single `first: 100` query misses threads beyond page 1:\n\n```bash\n# Step 1: get total thread count\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n    }\n  }\n}' | jq '.data.repository.pullRequest.reviewThreads.totalCount'\n\n# Step 2: paginate all pages, count truly unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes { isResolved }\n        }\n      }\n    }\n  }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved threads: $UNRESOLVED\"\n```\n\nOnly output `ORCHESTRATOR:DONE` after this loop reports 0.\n",".claude/skills/orchestrate/SKILL.md":"---\nname: orchestrate\ndescription: \"Meta-agent supervisor that manages a fleet of Claude Code agents running in tmux windows. Auto-discovers spare worktrees, spawns agents, monitors state, kicks idle agents, approves safe confirmations, and recycles worktrees when done. TRIGGER when user asks to supervise agents, run parallel tasks, manage worktrees, check agent status, or orchestrate parallel work.\"\nuser-invocable: true\nargument-hint: \"any free text — e.g. 'start 3 agents on X Y Z', 'show status', 'add task: implement feature A', 'stop', 'how many are free?'\"\nmetadata:\n  author: autogpt-team\n  version: \"6.0.0\"\n---\n\n# Orchestrate — Agent Fleet Supervisor\n\nOne tmux session, N windows — each window is one agent working in its own worktree. Speak naturally; Claude maps your intent to the right scripts.\n\n## Scripts\n\n```bash\nSKILLS_DIR=$(git rev-parse --show-toplevel)/.claude/skills/orchestrate/scripts\nSTATE_FILE=~/.claude/orchestrator-state.json\n```\n\n| Script | Purpose |\n|---|---|\n| `find-spare.sh [REPO_ROOT]` | List free worktrees — one `PATH BRANCH` per line |\n| `spawn-agent.sh SESSION PATH SPARE NEW_BRANCH OBJECTIVE [PR_NUMBER] [STEPS...]` | Create window + checkout branch + launch claude + send task. **Stdout: `SESSION:WIN` only** |\n| `recycle-agent.sh WINDOW PATH SPARE_BRANCH` | Kill window + restore spare branch |\n| `run-loop.sh` | **Mechanical babysitter** — idle restart + dialog approval + recycle on ORCHESTRATOR:DONE + supervisor health check + all-done notification |\n| `verify-complete.sh WINDOW` | Verify PR is done: checkpoints ✓ + 0 unresolved threads + CI green + no fresh CHANGES_REQUESTED. Repo auto-derived from state file `.repo` or git remote. |\n| `notify.sh MESSAGE` | Send notification via Discord webhook (env `DISCORD_WEBHOOK_URL` or state `.discord_webhook`), macOS notification center, and stdout |\n| `capacity.sh [REPO_ROOT]` | Print available + in-use worktrees |\n| `status.sh` | Print fleet status + live pane commands |\n| `poll-cycle.sh` | One monitoring cycle — classifies panes, tracks checkpoints, returns JSON action array |\n| `classify-pane.sh WINDOW` | Classify one pane state |\n\n## Supervision model\n\n```\nOrchestrating Claude (this Claude session — IS the supervisor)\n  └── Reads pane output, checks CI, intervenes with targeted guidance\n        run-loop.sh (separate tmux window, every 30s)\n          └── Mechanical only: idle restart, dialog approval, recycle on ORCHESTRATOR:DONE\n```\n\n**You (the orchestrating Claude)** are the supervisor. After spawning agents, stay in this conversation and actively monitor: poll each agent's pane every 2-3 minutes, check CI, nudge stalled agents, and verify completions. Do not spawn a separate supervisor Claude window — it loses context, is hard to observe, and compounds context compression problems.\n\n**run-loop.sh** is the mechanical layer — zero tokens, handles things that need no judgment: restart crashed agents, press Enter on dialogs, recycle completed worktrees (only after `verify-complete.sh` passes).\n\n## Checkpoint protocol\n\nAgents output checkpoints as they complete each required step:\n\n```\nCHECKPOINT:<step-name>\n```\n\nRequired steps are passed as args to `spawn-agent.sh` (e.g. `pr-address pr-test`). `run-loop.sh` will not recycle a window until all required checkpoints are found in the pane output. If `verify-complete.sh` fails, the agent is re-briefed automatically.\n\n## Worktree lifecycle\n\n```text\nspare/N branch  →  spawn-agent.sh (--session-id UUID)  →  window + feat/branch + claude running\n                                                                 ↓\n                                               CHECKPOINT:<step> (as steps complete)\n                                                                 ↓\n                                                        ORCHESTRATOR:DONE\n                                                                 ↓\n                                    verify-complete.sh: checkpoints ✓ + 0 threads + CI green + no fresh CHANGES_REQUESTED\n                                                                 ↓\n                                              state → \"done\", notify, window KEPT OPEN\n                                                                 ↓\n                              user/orchestrator explicitly requests recycle\n                                                                 ↓\n                                         recycle-agent.sh → spare/N (free again)\n```\n\n**Windows are never auto-killed.** The worktree stays on its branch, the session stays alive. The agent is done working but the window, git state, and Claude session are all preserved until you choose to recycle.\n\n**To resume a done or crashed session:**\n```bash\n# Resume by stored session ID (preferred — exact session, full context)\nclaude --resume SESSION_ID --permission-mode bypassPermissions\n\n# Or resume most recent session in that worktree directory\ncd /path/to/worktree && claude --continue --permission-mode bypassPermissions\n```\n\n**To manually recycle when ready:**\n```bash\nbash ~/.claude/orchestrator/scripts/recycle-agent.sh SESSION:WIN WORKTREE_PATH spare/N\n# Then update state:\njq --arg w \"SESSION:WIN\" '.agents |= map(if .window == $w then .state = \"recycled\" else . end)' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## State file (`~/.claude/orchestrator-state.json`)\n\nNever committed to git. You maintain this file directly using `jq` + atomic writes (`.tmp` → `mv`).\n\n```json\n{\n  \"active\": true,\n  \"tmux_session\": \"autogpt1\",\n  \"idle_threshold_seconds\": 300,\n  \"loop_window\": \"autogpt1:5\",\n  \"repo\": \"Significant-Gravitas/AutoGPT\",\n  \"discord_webhook\": \"https://discord.com/api/webhooks/...\",\n  \"last_poll_at\": 0,\n  \"agents\": [\n    {\n      \"window\": \"autogpt1:3\",\n      \"worktree\": \"AutoGPT6\",\n      \"worktree_path\": \"/path/to/AutoGPT6\",\n      \"spare_branch\": \"spare/6\",\n      \"branch\": \"feat/my-feature\",\n      \"objective\": \"Implement X and open a PR\",\n      \"pr_number\": \"12345\",\n      \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"steps\": [\"pr-address\", \"pr-test\"],\n      \"checkpoints\": [\"pr-address\"],\n      \"state\": \"running\",\n      \"last_output_hash\": \"\",\n      \"last_seen_at\": 0,\n      \"spawned_at\": 0,\n      \"idle_since\": 0,\n      \"revision_count\": 0,\n      \"last_rebriefed_at\": 0\n    }\n  ]\n}\n```\n\nTop-level optional fields:\n- `repo` — GitHub `owner/repo` for CI/thread checks. Auto-derived from git remote if omitted.\n- `discord_webhook` — Discord webhook URL for completion notifications. Also reads `DISCORD_WEBHOOK_URL` env var.\n\nPer-agent fields:\n- `session_id` — UUID passed to `claude --session-id` at spawn; use with `claude --resume UUID` to restore exact session context after a crash or window close.\n- `last_rebriefed_at` — Unix timestamp of last re-brief; enforces 5-min cooldown to prevent spam.\n\nAgent states: `running` | `idle` | `stuck` | `waiting_approval` | `complete` | `done` | `escalated`\n\n`done` means verified complete — window is still open, session still alive, worktree still on task branch. Not recycled yet.\n\n## Serial /pr-test rule\n\n`/pr-test` and `/pr-test --fix` run local Docker + integration tests that use shared ports, a shared database, and shared build caches. **Running two `/pr-test` jobs simultaneously will cause port conflicts and database corruption.**\n\n**Rule: only one `/pr-test` runs at a time. The orchestrator serializes them.**\n\nYou (the orchestrating Claude) own the test queue:\n1. Agents do `pr-review` and `pr-address` in parallel — that's safe (they only push code and reply to GitHub).\n2. When a PR needs local testing, add it to your mental queue — don't give agents a `pr-test` step.\n3. Run `/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER --fix` yourself, sequentially.\n4. Feed results back to the relevant agent via `tmux send-keys`:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"Local tests for PR #N: <paste failure output or 'all passed'>. Fix any failures and push, then output ORCHESTRATOR:DONE.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n5. Wait for CI to confirm green before marking the agent done.\n\nIf multiple PRs need testing at the same time, pick the one furthest along (fewest pending CI checks) and test it first. Only start the next test after the previous one completes.\n\n## Session restore (tested and confirmed)\n\nAgent sessions are saved to disk. To restore a closed or crashed session:\n\n```bash\n# If session_id is in state (preferred):\nNEW_WIN=$(tmux new-window -t SESSION -n WORKTREE_NAME -P -F '#{window_index}')\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --resume SESSION_ID --permission-mode bypassPermissions\" Enter\n\n# If no session_id (use --continue for most recent session in that directory):\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --continue --permission-mode bypassPermissions\" Enter\n```\n\n`--continue` restores the full conversation history including all tool calls, file edits, and context. The agent resumes exactly where it left off. After restoring, update the window address in the state file:\n\n```bash\njq --arg old \"SESSION:OLD_WIN\" --arg new \"SESSION:NEW_WIN\" \\\n  '(.agents[] | select(.window == $old)).window = $new' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## Intent → action mapping\n\nMatch the user's message to one of these intents:\n\n| The user says something like… | What to do |\n|---|---|\n| \"status\", \"what's running\", \"show agents\" | Run `status.sh` + `capacity.sh`, show output |\n| \"how many free\", \"capacity\", \"available worktrees\" | Run `capacity.sh`, show output |\n| \"start N agents on X, Y, Z\" or \"run these tasks: …\" | See **Spawning agents** below |\n| \"add task: …\", \"add one more agent for …\" | See **Adding an agent** below |\n| \"stop\", \"shut down\", \"pause the fleet\" | See **Stopping** below |\n| \"poll\", \"check now\", \"run a cycle\" | Run `poll-cycle.sh`, process actions |\n| \"recycle window X\", \"free up autogpt3\" | Run `recycle-agent.sh` directly |\n\nWhen the intent is ambiguous, show capacity first and ask what tasks to run.\n\n## Spawning agents\n\n### 1. Resolve tmux session\n\n```bash\ntmux list-sessions -F \"#{session_name}: #{session_windows} windows\" 2>/dev/null\n```\n\nUse an existing session. **Never create a tmux session from within Claude** — it becomes a child of Claude's process and dies when the session ends. If no session exists, tell the user to run `tmux new-session -d -s autogpt1` in their terminal first, then re-invoke `/orchestrate`.\n\n### 2. Show available capacity\n\n```bash\nbash $SKILLS_DIR/capacity.sh $(git rev-parse --show-toplevel)\n```\n\n### 3. Collect tasks from the user\n\nFor each task, gather:\n- **objective** — what to do (e.g. \"implement feature X and open a PR\")\n- **branch name** — e.g. `feat/my-feature` (derive from objective if not given)\n- **pr_number** — GitHub PR number if working on an existing PR (for verification)\n- **steps** — required checkpoint names in order (e.g. `pr-address pr-test`) — derive from objective\n\nAsk for `idle_threshold_seconds` only if the user mentions it (default: 300).\n\nNever ask the user to specify a worktree — auto-assign from `find-spare.sh`.\n\n### 4. Spawn one agent per task\n\n```bash\n# Get ordered list of spare worktrees\nSPARE_LIST=$(bash $SKILLS_DIR/find-spare.sh $(git rev-parse --show-toplevel))\n\n# For each task, take the next spare line:\nWORKTREE_PATH=$(echo \"$SPARE_LINE\" | awk '{print $1}')\nSPARE_BRANCH=$(echo \"$SPARE_LINE\" | awk '{print $2}')\n\n# With PR number and required steps:\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\" \"$PR_NUMBER\" \"pr-address\" \"pr-test\")\n\n# Without PR (new work):\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\")\n```\n\nBuild an agent record and append it to the state file. If the state file doesn't exist yet, initialize it:\n\n```bash\n# Derive repo from git remote (used by verify-complete.sh + supervisor)\nREPO=$(git remote get-url origin 2>/dev/null | sed 's|.*github\\.com[:/]||; s|\\.git$||' || echo \"\")\n\njq -n \\\n  --arg session \"$SESSION\" \\\n  --arg repo \"$REPO\" \\\n  --argjson threshold 300 \\\n  '{active:true, tmux_session:$session, idle_threshold_seconds:$threshold,\n    repo:$repo, loop_window:null, supervisor_window:null, last_poll_at:0, agents:[]}' \\\n  > ~/.claude/orchestrator-state.json\n```\n\nOptionally add a Discord webhook for completion notifications:\n```bash\njq --arg hook \"$DISCORD_WEBHOOK_URL\" '.discord_webhook = $hook' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n`spawn-agent.sh` writes the initial agent record (window, worktree_path, branch, objective, state, etc.) to the state file automatically — **do not append the record again after calling it.** The record already exists and `pr_number`/`steps` are patched in by the script itself.\n\n### 5. Start the mechanical babysitter\n\n```bash\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\n\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n### 6. Begin supervising directly in this conversation\n\nYou are the supervisor. After spawning, immediately start your first poll loop (see **Supervisor duties** below) and continue every 2-3 minutes. Do NOT spawn a separate supervisor Claude window.\n\n## Adding an agent\n\nFind the next spare worktree, then spawn and append to state — same as steps 2–4 above but for a single task. If no spare worktrees are available, tell the user.\n\n## Supervisor duties (YOUR job, every 2-3 min in this conversation)\n\nYou are the supervisor. Run this poll loop directly in your Claude session — not in a separate window.\n\n### Poll loop mechanism\n\nYou are reactive — you only act when a tool completes or the user sends a message. To create a self-sustaining poll loop without user involvement:\n\n1. Start each poll with `run_in_background: true` + a sleep before the work:\n   ```bash\n   sleep 120 && tmux capture-pane -t autogpt1:0 -p -S -200 | tail -40\n   # + similar for each active window\n   ```\n2. When the background job notifies you, read the pane output and take action.\n3. Immediately schedule the next background poll — this keeps the loop alive.\n4. Stop scheduling when all agents are done/escalated.\n\n**Never tell the user \"I'll poll every 2-3 minutes\"** — that does nothing without a trigger. Start the background job instead.\n\n### Each poll: what to check\n\n```bash\n# 1. Read state\ncat ~/.claude/orchestrator-state.json | jq '.agents[] | {window, worktree, branch, state, pr_number, checkpoints}'\n\n# 2. For each running/stuck/idle agent, capture pane\ntmux capture-pane -t SESSION:WIN -p -S -200 | tail -60\n```\n\nFor each agent, decide:\n\n| What you see | Action |\n|---|---|\n| Spinner / tools running | Do nothing — agent is working |\n| Idle `❯` prompt, no `ORCHESTRATOR:DONE` | Stalled — send specific nudge with objective from state |\n| Stuck in error loop | Send targeted fix with exact error + solution |\n| Waiting for input / question | Answer and unblock via `tmux send-keys` |\n| CI red | `gh pr checks PR_NUMBER --repo REPO` → tell agent exactly what's failing |\n| GitHub abuse rate limit error | Nudge: \"Wait 60 seconds then continue posting replies with sleep 3 between each\" |\n| Context compacted / agent lost | Send recovery: `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"WIN\")'` + `gh pr view PR_NUMBER --json title,body` |\n| `ORCHESTRATOR:DONE` in output | Query GraphQL for actual unresolved count. If >0, re-brief. If 0, run `verify-complete.sh` |\n\n**Poll all windows from state, not from memory.** Before each poll, run:\n```bash\njq -r '.agents[] | select(.state | test(\"running|idle|stuck|waiting_approval|pending_evaluation\")) | .window' ~/.claude/orchestrator-state.json\n```\nand capture every window listed. If you manually added a window outside spawn-agent.sh, ensure it's in the state file first.\n\n### RUNNING count includes waiting_approval agents\n\nThe `RUNNING` count from run-loop.sh includes agents in `waiting_approval` state (they match the regex `running|stuck|waiting_approval|idle`). This means a fleet that is only `waiting_approval` still shows RUNNING > 0 in the log — it does **not** mean agents are actively working.\n\nWhen you see `RUNNING > 0` in the run-loop log but suspect agents are actually blocked, check state directly:\n```bash\njq '.agents[] | {window, state, worktree}' ~/.claude/orchestrator-state.json\n```\nA count of `running=1 waiting=1` in the log actually means one agent is waiting for approval — the orchestrator should check and approve, not wait.\n\n### State file staleness recovery\n\nThe state file is written by scripts but can drift from reality when windows are closed, sessions expire, or the orchestrator restarts across conversations.\n\n**Signs of stale state:**\n- `loop_window` points to a window that no longer exists in the tmux session\n- An agent's `state` is `running` but tmux window is closed or shows a shell prompt (not claude)\n- `last_seen_at` is hours old but state still says `running`\n\n**Recovery steps:**\n\n1. **Verify actual tmux windows:**\n```bash\ntmux list-windows -t SESSION -F '#{window_index}: #{window_name} (#{pane_current_command})'\n```\n\n2. **Cross-reference with state file:**\n```bash\njq -r '.agents[] | \"\\(.window) \\(.state) \\(.worktree)\"' ~/.claude/orchestrator-state.json\n```\n\n3. **Fix stale entries:**\n```bash\n# Agent window closed — mark idle so run-loop.sh will restart it\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window==$w)).state = \"idle\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\n# loop_window gone — kill the stale reference, then restart run-loop.sh\njq '.loop_window = null' ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n4. **After any state repair, re-run `status.sh` to confirm coherence before resuming supervision.**\n\n### Strict ORCHESTRATOR:DONE gate\n\n`verify-complete.sh` handles the main checks automatically (checkpoints, threads, CI green, spawned_at, and CHANGES_REQUESTED). Run it:\n\n**CHANGES_REQUESTED staleness rule**: a `CHANGES_REQUESTED` review only blocks if it was submitted *after* the latest commit. If the latest commit postdates the review, the review is considered stale (feedback already addressed) and does not block. This avoids false negatives when a bot reviewer hasn't re-reviewed after the agent's fixing commits.\n\n```bash\nSKILLS_DIR=~/.claude/orchestrator/scripts\nbash $SKILLS_DIR/verify-complete.sh SESSION:WIN\n```\n\nIf it passes → run-loop.sh will recycle the window automatically. No manual action needed.\nIf it fails → re-brief the agent with the failure reason. Never manually mark state `done` to bypass this.\n\n### Re-brief a stalled agent\n\n**Before sending any nudge, verify the pane is at an idle ❯ prompt.** Sending text into a still-processing pane produces stuck `[Pasted text +N lines]` that the agent never sees.\n\nCheck:\n```bash\ntmux capture-pane -t SESSION:WIN -p 2>/dev/null | tail -5\n```\nIf the last line shows a spinner (✳✽✢✶·), `Running…`, or no `❯` — wait 10–15s and check again before sending.\n\n```bash\nOBJ=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .objective' ~/.claude/orchestrator-state.json)\nPR=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .pr_number' ~/.claude/orchestrator-state.json)\ntmux send-keys -t SESSION:WIN \"You appear stalled. Your objective: $OBJ. Check: gh pr view $PR --json title,body,headRefName to reorient.\"\nsleep 0.3\ntmux send-keys -t SESSION:WIN Enter\n```\n\nIf `image_path` is set on the agent record, include: \"Re-read context at IMAGE_PATH with the Read tool.\"\n\n## Self-recovery protocol (agents)\n\nspawn-agent.sh automatically includes this instruction in every objective:\n\n> If your context compacts and you lose track of what to do, run:\n> `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"SESSION:WIN\")'`\n> and `gh pr view PR_NUMBER --json title,body,headRefName` to reorient.\n> Output each completed step as `CHECKPOINT:<step-name>` on its own line.\n\n## Passing images and screenshots to agents\n\n`tmux send-keys` is text-only — you cannot paste a raw image into a pane. To give an agent visual context (screenshots, diagrams, mockups):\n\n1. **Save the image to a temp file** with a stable path:\n   ```bash\n   # If the user drags in a screenshot or you receive a file path:\n   IMAGE_PATH=\"/tmp/orchestrator-context-$(date +%s).png\"\n   cp \"$USER_PROVIDED_PATH\" \"$IMAGE_PATH\"\n   ```\n\n2. **Reference the path in the objective string**:\n   ```bash\n   OBJECTIVE=\"Implement the layout shown in /tmp/orchestrator-context-1234567890.png. Read that image first with the Read tool to understand the design.\"\n   ```\n\n3. The agent uses its `Read` tool to view the image at startup — Claude Code agents are multimodal and can read image files directly.\n\n**Rule**: always use `/tmp/orchestrator-context-<timestamp>.png` as the naming convention so the supervisor knows what to look for if it needs to re-brief an agent with the same image.\n\n---\n\n## Orchestrator final evaluation (YOU decide, not the script)\n\n`verify-complete.sh` is a gate — it blocks premature marking. But it cannot tell you if the work is actually good. That is YOUR job.\n\nWhen run-loop marks an agent `pending_evaluation` and you're notified, do all of these before marking done:\n\n### 1. Run /pr-test (required, serialized, use TodoWrite to queue)\n\n`/pr-test` is the only reliable confirmation that the objective is actually met. Run it yourself, not the agent.\n\n**When multiple PRs reach `pending_evaluation` at the same time, use TodoWrite to queue them:**\n```\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/NNNN — <feature description>\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/MMMM — <feature description>\n```\nRun one at a time. Check off as you go.\n\n```\n/pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/PR_NUMBER\n```\n\n**/pr-test can be lazy** — if it gives vague output, re-run with full context:\n\n```\n/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER\nContext: This PR implements <objective from state file>. Key files: <list>.\nPlease verify: <specific behaviors to check>.\n```\n\nOnly one `/pr-test` at a time — they share ports and DB.\n\n### /pr-test result evaluation\n\n**PARTIAL on any headline feature scenario is an immediate blocker.** Do not approve, do not mark done, do not let the agent output `ORCHESTRATOR:DONE`.\n\n| `/pr-test` result | Action |\n|---|---|\n| All headline scenarios **PASS** | Proceed to evaluation step 2 |\n| Any headline scenario **PARTIAL** | Re-brief the agent immediately — see below |\n| Any headline scenario **FAIL** | Re-brief the agent immediately |\n\n**What PARTIAL means**: the feature is only partly working. Example: the Apply button never appeared, or the AI returned no action blocks. The agent addressed part of the objective but not all of it.\n\n**When any headline scenario is PARTIAL or FAIL:**\n\n1. Do NOT mark the agent done or accept `ORCHESTRATOR:DONE`\n2. Re-brief the agent with the specific scenario that failed and what was missing:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"PARTIAL result on /pr-test — S5 (Apply button) never appeared. The AI must output JSON action blocks for the Apply button to render. Fix this before re-running /pr-test.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n3. Set state back to `running`:\n   ```bash\n   jq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"running\"' \\\n     ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n   ```\n4. Wait for new `ORCHESTRATOR:DONE`, then re-run `/pr-test` from scratch\n\n**Rule: only ALL-PASS qualifies for approval.** A mix of PASS + PARTIAL is a failure.\n\n> **Why this matters**: A PR was once wrongly approved with S5 PARTIAL — the AI never output JSON action blocks so the Apply button never appeared. The fix was already in the agent's reach but slipped through because PARTIAL was not treated as blocking.\n\n### 2. Do your own evaluation\n\n1. **Read the PR diff and objective** — does the code actually implement what was asked? Is anything obviously missing or half-done?\n2. **Read the resolved threads** — were comments addressed with real fixes, or just dismissed/resolved without changes?\n3. **Check CI run names** — any suspicious retries that shouldn't have passed?\n4. **Check the PR description** — title, summary, test plan complete?\n\n### 3. Decide\n\n- `/pr-test` all scenarios PASS + evaluation looks good → mark `done` in state, tell the user the PR is ready, ask if window should be closed\n- `/pr-test` any scenario PARTIAL or FAIL → re-brief the agent with the specific failing scenario, set state back to `running` (see `/pr-test result evaluation` above)\n- Evaluation finds gaps even with all PASS → re-brief the agent with specific gaps, set state back to `running`\n\n**Never mark done based purely on script output.** You hold the full objective context; the script does not.\n\n```bash\n# Mark done after your positive evaluation:\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"done\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## When to stop the fleet\n\nStop the fleet (`active = false`) when **all** of the following are true:\n\n| Check | How to verify |\n|---|---|\n| All agents are `done` or `escalated` | `jq '[.agents[] | select(.state | test(\"running\\|stuck\\|idle\\|waiting_approval\"))] | length' ~/.claude/orchestrator-state.json` == 0 |\n| All PRs have 0 unresolved review threads | GraphQL `isResolved` check per PR |\n| All PRs have green CI **on a run triggered after the agent's last push** | `gh run list --branch BRANCH --limit 1` timestamp > `spawned_at` in state |\n| No fresh CHANGES_REQUESTED (after latest commit) | `verify-complete.sh` checks this — stale pre-commit reviews are ignored |\n| No agents are `escalated` without human review | If any are escalated, surface to user first |\n\n**Do NOT stop just because agents output `ORCHESTRATOR:DONE`.** That is a signal to verify, not a signal to stop.\n\n**Do stop** if the user explicitly says \"stop\", \"shut down\", or \"kill everything\", even with agents still running.\n\n```bash\n# Graceful stop\njq '.active = false' ~/.claude/orchestrator-state.json > /tmp/orch.tmp \\\n  && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\nLOOP_WINDOW=$(jq -r '.loop_window // \"\"' ~/.claude/orchestrator-state.json)\n[ -n \"$LOOP_WINDOW\" ] && tmux kill-window -t \"$LOOP_WINDOW\" 2>/dev/null || true\n```\n\nDoes **not** recycle running worktrees — agents may still be mid-task. Run `capacity.sh` to see what's still in progress.\n\n## tmux send-keys pattern\n\n**Always split long messages into text + Enter as two separate calls with a sleep between them.** If sent as one call (`\"text\" Enter`), Enter can fire before the full string is buffered into Claude's input — leaving the message stuck as `[Pasted text +N lines]` unsent.\n\n```bash\n# CORRECT — text then Enter separately\ntmux send-keys -t \"$WINDOW\" \"your long message here\"\nsleep 0.3\ntmux send-keys -t \"$WINDOW\" Enter\n\n# WRONG — Enter may fire before text is buffered\ntmux send-keys -t \"$WINDOW\" \"your long message here\" Enter\n```\n\nShort single-character sends (`y`, `Down`, empty Enter for dialog approval) are safe to combine since they have no buffering lag.\n\n---\n\n## Protected worktrees\n\nSome worktrees must **never** be used as spare worktrees for agent tasks because they host files critical to the orchestrator itself:\n\n| Worktree | Protected branch | Why |\n|---|---|---|\n| `AutoGPT1` | `dx/orchestrate-skill` | Hosts the orchestrate skill scripts. `recycle-agent.sh` would check out `spare/1`, wiping `.claude/skills/` and breaking all subsequent `spawn-agent.sh` calls. |\n\n**Rule**: when selecting spare worktrees via `find-spare.sh`, skip any worktree whose CURRENT branch matches a protected branch. If you accidentally spawn an agent in a protected worktree, do not let `recycle-agent.sh` run on it — manually restore the branch after the agent finishes.\n\nWhen `dx/orchestrate-skill` is merged into `dev`, `AutoGPT1` becomes a normal spare again.\n\n---\n\n## Thread resolution integrity (critical)\n\n**Agents MUST NOT resolve review threads via GraphQL unless a real code fix has been committed and pushed first.**\n\nThis is the most common failure mode: agents call `resolveReviewThread` to make unresolved counts drop without actually fixing anything. This produces a false \"done\" signal that gets past verify-complete.sh.\n\n**The only valid resolution sequence:**\n1. Read the thread and understand what it's asking\n2. Make the actual code change\n3. `git commit` and `git push`\n4. Reply to the thread with the commit SHA (e.g. \"Fixed in `abc1234`\")\n5. THEN call `resolveReviewThread`\n\n**The supervisor must verify actual thread counts via GraphQL** — never trust an agent's claim of \"0 unresolved.\" After any agent's ORCHESTRATOR:DONE, always run:\n\n```bash\n# Step 1: get total count\nTOTAL=$(gh api graphql -f query='{ repository(owner: \"OWNER\", name: \"REPO\") { pullRequest(number: PR) { reviewThreads { totalCount } } } }' \\\n  | jq '.data.repository.pullRequest.reviewThreads.totalCount')\necho \"Total threads: $TOTAL\"\n\n# Step 2: paginate all pages and count unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"{ repository(owner: \\\"OWNER\\\", name: \\\"REPO\\\") { pullRequest(number: PR) { reviewThreads(first: 100${AFTER}) { pageInfo { hasNextPage endCursor } nodes { isResolved } } } } }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved: $UNRESOLVED\"\n```\n\nIf unresolved > 0, the agent is NOT done — re-brief with the actual count and the rule.\n\n**Include this in every agent objective:**\n> IMPORTANT: Do NOT resolve any review thread via GraphQL unless the code fix is committed and pushed first. Fix the code → commit → push → reply with SHA → then resolve. Never resolve without a real commit. \"Accepted\" or \"Acknowledged\" replies are NOT resolutions — only real commits qualify.\n\n### Detecting fake resolutions\n\nWhen an agent claims \"0 unresolved threads\", query GitHub GraphQL yourself and also inspect how each thread was resolved. A resolved thread whose last comment is `\"Acknowledged\"`, `\"Same as above\"`, `\"Accepted trade-off\"`, or `\"Deferred\"` — with no commit SHA — is a fake resolution.\n\nTo spot these, paginate all pages and collect resolved threads with missing SHA links:\n```bash\n# Paginate all pages — first:100 misses threads beyond page 1 on large PRs\nCURSOR=\"\"; FAKE_RESOLUTIONS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: PR_NUMBER) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved\n            comments(last: 1) {\n              nodes { body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  PAGE_FAKES=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[]\n    | select(.isResolved == true)\n    | {body: .comments.nodes[0].body[:120], author: .comments.nodes[0].author.login}\n    | select(.body | test(\"Fixed in|Removed in|Addressed in\") | not)]')\n  FAKE_RESOLUTIONS=$(echo \"$FAKE_RESOLUTIONS $PAGE_FAKES\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"$FAKE_RESOLUTIONS\"\n```\nAny resolved thread whose last comment does NOT contain `\"Fixed in\"`, `\"Removed in\"`, or `\"Addressed in\"` (with a commit link) should be investigated — either the agent falsely resolved it, or it was a genuine false positive that needs explanation.\n\n## GitHub abuse rate limits\n\nTwo distinct rate limits exist with different recovery times:\n\n| Error | HTTP status | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` in body | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `API rate limit exceeded` | 429 | Primary rate limit — too many read calls per hour | Wait until `X-RateLimit-Reset` timestamp |\n\n**Prevention:** Agents must add `sleep 3` between individual thread reply API calls. For >20 unresolved threads, increase to `sleep 5`.\n\nIf you see a 403 `abuse` error from an agent's pane:\n1. Nudge the agent: `\"You hit a GitHub secondary rate limit (403). Stop all API writes. Wait 2 minutes, then resume with sleep 3 between each thread reply.\"`\n2. Do NOT nudge again during the 2-minute wait — a second nudge restarts the clock.\n\nAdd this to agent briefings when there are >20 unresolved threads:\n> Post replies with `sleep 3` between each reply. If you hit a 403 abuse error, wait 2 minutes (not 60s — secondary limits take longer to clear) then continue.\n\n## Key rules\n\n1. **Scripts do all the heavy lifting** — don't reimplement their logic inline in this file\n2. **Never ask the user to pick a worktree** — auto-assign from `find-spare.sh` output\n3. **Never restart a running agent** — only restart on `idle` kicks (foreground is a shell)\n4. **Auto-dismiss settings dialogs** — if \"Enter to confirm\" appears, send Down+Enter\n5. **Always `--permission-mode bypassPermissions`** on every spawn\n6. **Escalate after 3 kicks** — mark `escalated`, surface to user\n7. **Atomic state writes** — always write to `.tmp` then `mv`\n8. **Never approve destructive commands** outside the worktree scope — when in doubt, escalate\n9. **Never recycle without verification** — `verify-complete.sh` must pass before recycling\n10. **No TASK.md files** — commit risk; use state file + `gh pr view` for agent context persistence\n11. **Re-brief stalled agents** — read objective from state file + `gh pr view`, send via tmux\n12. **ORCHESTRATOR:DONE is a signal to verify, not to accept** — always run `verify-complete.sh` and check CI run timestamp before recycling\n13. **Protected worktrees** — never use the worktree hosting the skill scripts as a spare\n14. **Images via file path** — save screenshots to `/tmp/orchestrator-context-<ts>.png`, pass path in objective; agents read with the `Read` tool\n15. **Split send-keys** — always separate text and Enter with `sleep 0.3` between calls for long strings\n16. **Poll ALL windows from state file** — never hardcode window count. Derive active windows dynamically: `jq -r '.agents[] | select(.state | test(\"running|idle|stuck\")) | .window' ~/.claude/orchestrator-state.json`. If you added a window mid-session outside spawn-agent.sh, add it to the state file immediately.\n20. **Orchestrator handles its own approvals** — when spawning a subagent to make edits (SKILL.md, scripts, config), review the diff yourself and approve/reject without surfacing it to the user. The user should never have to open a file to check the orchestrator's work. Use the Agent tool with `subagent_type: general-purpose` for drafting, then verify the result yourself before considering the task done.\n17. **Update state file on re-task** — whenever an agent is re-tasked mid-session (objective changes, new PR assigned), update the state file record immediately so objectives stay accurate for re-briefing after compaction.\n18. **No GraphQL resolveReviewThread without a commit** — see Thread resolution integrity above. This is rule #1 for pr-address work.\n19. **Verify thread counts yourself** — after any agent claims \"0 unresolved threads\", query GitHub GraphQL directly before accepting. Never trust the agent's self-report.\n",".claude/skills/worktree/SKILL.md":"---\nname: worktree\ndescription: Set up a new git worktree for parallel development. Creates the worktree, copies .env files, installs dependencies, and generates Prisma client. TRIGGER when user asks to set up a worktree, work on a branch in isolation, or needs a separate environment for a branch or PR.\nuser-invocable: true\nargs: \"[name] — optional worktree name (e.g., 'AutoGPT7'). If omitted, uses next available AutoGPT<N>.\"\nmetadata:\n  author: autogpt-team\n  version: \"3.0.0\"\n---\n\n# Worktree Setup\n\n## Create the worktree\n\nDerive paths from the git toplevel. If a name is provided as argument, use it. Otherwise, check `git worktree list` and pick the next `AutoGPT<N>`.\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nPARENT=$(dirname \"$ROOT\")\n\n# From an existing branch\ngit worktree add \"$PARENT/<NAME>\" <branch-name>\n\n# From a new branch off dev\ngit worktree add -b <new-branch> \"$PARENT/<NAME>\" dev\n```\n\n## Copy environment files\n\nCopy `.env` from the root worktree. Falls back to `.env.default` if `.env` doesn't exist.\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nTARGET=\"$(dirname \"$ROOT\")/<NAME>\"\n\nfor envpath in autogpt_platform/backend autogpt_platform/frontend autogpt_platform; do\n  if [ -f \"$ROOT/$envpath/.env\" ]; then\n    cp \"$ROOT/$envpath/.env\" \"$TARGET/$envpath/.env\"\n  elif [ -f \"$ROOT/$envpath/.env.default\" ]; then\n    cp \"$ROOT/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n  fi\ndone\n```\n\n## Install dependencies\n\n```bash\nTARGET=\"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\ncd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install\ncd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate\ncd \"$TARGET/autogpt_platform/frontend\" && pnpm install\n```\n\nReplace `<NAME>` with the actual worktree name (e.g., `AutoGPT7`).\n\n## Running the app (optional)\n\nBackend uses ports: 8001, 8002, 8003, 8005, 8006, 8007, 8008. Free them first if needed:\n\n```bash\nTARGET=\"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\nfor port in 8001 8002 8003 8005 8006 8007 8008; do\n  lsof -ti :$port | xargs kill -9 2>/dev/null || true\ndone\ncd \"$TARGET/autogpt_platform/backend\" && poetry run app\n```\n\n## CoPilot testing\n\nSDK mode spawns a Claude subprocess — won't work inside Claude Code. Set `CHAT_USE_CLAUDE_AGENT_SDK=false` in `backend/.env` to use baseline mode.\n\n## Cleanup\n\n```bash\n# Replace <NAME> with the actual worktree name (e.g., AutoGPT7)\ngit worktree remove \"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\n```\n\n## Alternative: Branchlet (optional)\n\nIf [branchlet](https://www.npmjs.com/package/branchlet) is installed:\n\n```bash\nbranchlet create -n <name> -s <source-branch> -b <new-branch>\n```\n","autogpt_platform/backend/test/fixtures/skills/webapp-testing/SKILL.md":"---\nname: webapp-testing\ndescription: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.\nlicense: Complete terms in LICENSE.txt\n---\n\n# Web Application Testing\n\nTo test local web applications, write native Python Playwright scripts.\n\n**Helper Scripts Available**:\n- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)\n\n**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.\n\n## Decision Tree: Choosing Your Approach\n\n```\nUser task → Is it static HTML?\n    ├─ Yes → Read HTML file directly to identify selectors\n    │         ├─ Success → Write Playwright script using selectors\n    │         └─ Fails/Incomplete → Treat as dynamic (below)\n    │\n    └─ No (dynamic webapp) → Is the server already running?\n        ├─ No → Run: python scripts/with_server.py --help\n        │        Then use the helper + write simplified Playwright script\n        │\n        └─ Yes → Reconnaissance-then-action:\n            1. Navigate and wait for networkidle\n            2. Take screenshot or inspect DOM\n            3. Identify selectors from rendered state\n            4. Execute actions with discovered selectors\n```\n\n## Example: Using with_server.py\n\nTo start a server, run `--help` first, then use the helper:\n\n**Single server:**\n```bash\npython scripts/with_server.py --server \"npm run dev\" --port 5173 -- python your_automation.py\n```\n\n**Multiple servers (e.g., backend + frontend):**\n```bash\npython scripts/with_server.py \\\n  --server \"cd backend && python server.py\" --port 3000 \\\n  --server \"cd frontend && npm run dev\" --port 5173 \\\n  -- python your_automation.py\n```\n\nTo create an automation script, include only Playwright logic (servers are managed automatically):\n```python\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode\n    page = browser.new_page()\n    page.goto('http://localhost:5173') # Server already running and ready\n    page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute\n    # ... your automation logic\n    browser.close()\n```\n\n## Reconnaissance-Then-Action Pattern\n\n1. **Inspect rendered DOM**:\n   ```python\n   page.screenshot(path='/tmp/inspect.png', full_page=True)\n   content = page.content()\n   page.locator('button').all()\n   ```\n\n2. **Identify selectors** from inspection results\n\n3. **Execute actions** using discovered selectors\n\n## Common Pitfall\n\n❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps\n✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection\n\n## Best Practices\n\n- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. \n- Use `sync_playwright()` for synchronous scripts\n- Always close the browser when done\n- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs\n- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`\n\n## Reference Files\n\n- **examples/** - Examples showing common patterns:\n  - `element_discovery.py` - Discovering buttons, links, and inputs on a page\n  - `static_html_automation.py` - Using file:// URLs for local HTML\n  - `console_logging.py` - Capturing console logs during automation",".claude/skills/setup-repo/SKILL.md":"---\nname: setup-repo\ndescription: Initialize a worktree-based repo layout for parallel development. Creates a main worktree, a reviews worktree for PR reviews, and N numbered work branches. Handles .env creation, dependency installation, and branchlet config. TRIGGER when user asks to set up the repo from scratch, initialize worktrees, bootstrap their dev environment, \"setup repo\", \"setup worktrees\", \"initialize dev environment\", \"set up branches\", or when a freshly cloned repo has no sibling worktrees.\nuser-invocable: true\nargs: \"No arguments — interactive setup via prompts.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Repository Setup\n\nThis skill sets up a worktree-based development layout from a freshly cloned repo. It creates:\n- A **main** worktree (the primary checkout)\n- A **reviews** worktree (for PR reviews)\n- **N work branches** (branch1..branchN) for parallel development\n\n## Step 1: Identify the repo\n\nDetermine the repo root and parent directory:\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nREPO_NAME=$(basename \"$ROOT\")\nPARENT=$(dirname \"$ROOT\")\n```\n\nDetect if the repo is already inside a worktree layout by counting sibling worktrees (not just checking the directory name, which could be anything):\n\n```bash\n# Count worktrees that are siblings (live under $PARENT but aren't $ROOT itself)\nSIBLING_COUNT=$(git worktree list --porcelain 2>/dev/null | grep \"^worktree \" | grep -c \"$PARENT/\" || true)\nif [ \"$SIBLING_COUNT\" -gt 1 ]; then\n  echo \"INFO: Existing worktree layout detected at $PARENT ($SIBLING_COUNT worktrees)\"\n  # Use $ROOT as-is; skip renaming/restructuring\nelse\n  echo \"INFO: Fresh clone detected, proceeding with setup\"\nfi\n```\n\n## Step 2: Ask the user questions\n\nUse AskUserQuestion to gather setup preferences:\n\n1. **How many parallel work branches do you need?** (Options: 4, 8, 16, or custom)\n   - These become `branch1` through `branchN`\n2. **Which branch should be the base?** (Options: origin/master, origin/dev, or custom)\n   - All work branches and reviews will start from this\n\n## Step 3: Fetch and set up branches\n\n```bash\ncd \"$ROOT\"\ngit fetch origin\n\n# Create the reviews branch from base (skip if already exists)\nif git show-ref --verify --quiet refs/heads/reviews; then\n  echo \"INFO: Branch 'reviews' already exists, skipping\"\nelse\n  git branch reviews <base-branch>\nfi\n\n# Create numbered work branches from base (skip if already exists)\nfor i in $(seq 1 \"$COUNT\"); do\n  if git show-ref --verify --quiet \"refs/heads/branch$i\"; then\n    echo \"INFO: Branch 'branch$i' already exists, skipping\"\n  else\n    git branch \"branch$i\" <base-branch>\n  fi\ndone\n```\n\n## Step 4: Create worktrees\n\nCreate worktrees as siblings to the main checkout:\n\n```bash\nif [ -d \"$PARENT/reviews\" ]; then\n  echo \"INFO: Worktree '$PARENT/reviews' already exists, skipping\"\nelse\n  git worktree add \"$PARENT/reviews\" reviews\nfi\n\nfor i in $(seq 1 \"$COUNT\"); do\n  if [ -d \"$PARENT/branch$i\" ]; then\n    echo \"INFO: Worktree '$PARENT/branch$i' already exists, skipping\"\n  else\n    git worktree add \"$PARENT/branch$i\" \"branch$i\"\n  fi\ndone\n```\n\n## Step 5: Set up environment files\n\n**Do NOT assume .env files exist.** For each worktree (including main if needed):\n\n1. Check if `.env` exists in the source worktree for each path\n2. If `.env` exists, copy it\n3. If only `.env.default` or `.env.example` exists, copy that as `.env`\n4. If neither exists, warn the user and list which env files are missing\n\nEnv file locations to check (same as the `/worktree` skill — keep these in sync):\n- `autogpt_platform/.env`\n- `autogpt_platform/backend/.env`\n- `autogpt_platform/frontend/.env`\n\n> **Note:** This env copying logic intentionally mirrors the `/worktree` skill's approach. If you update the path list or fallback logic here, update `/worktree` as well.\n\n```bash\nSOURCE=\"$ROOT\"\nWORKTREES=\"reviews\"\nfor i in $(seq 1 \"$COUNT\"); do WORKTREES=\"$WORKTREES branch$i\"; done\n\nFOUND_ANY_ENV=0\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  for envpath in autogpt_platform autogpt_platform/backend autogpt_platform/frontend; do\n    if [ -f \"$SOURCE/$envpath/.env\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env\" \"$TARGET/$envpath/.env\"\n    elif [ -f \"$SOURCE/$envpath/.env.default\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.default — you may need to edit it\"\n    elif [ -f \"$SOURCE/$envpath/.env.example\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.example\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.example — you may need to edit it\"\n    else\n      echo \"WARNING: No .env, .env.default, or .env.example found at $SOURCE/$envpath/\"\n    fi\n  done\ndone\n\nif [ \"$FOUND_ANY_ENV\" -eq 0 ]; then\n  echo \"WARNING: No environment files or templates were found in the source worktree.\"\n  # Use AskUserQuestion to confirm: \"Continue setup without env files?\"\n  # If the user declines, stop here and let them set up .env files first.\nfi\n```\n\n## Step 6: Copy branchlet config\n\nCopy `.branchlet.json` from main to each worktree so branchlet can manage sub-worktrees:\n\n```bash\nif [ -f \"$ROOT/.branchlet.json\" ]; then\n  for wt in $WORKTREES; do\n    cp \"$ROOT/.branchlet.json\" \"$PARENT/$wt/.branchlet.json\"\n  done\nfi\n```\n\n## Step 7: Install dependencies\n\nInstall deps in all worktrees. Run these sequentially per worktree:\n\n```bash\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  echo \"=== Installing deps for $wt ===\"\n  (cd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install) &&\n  (cd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate) &&\n  (cd \"$TARGET/autogpt_platform/frontend\" && pnpm install) &&\n  echo \"=== Done: $wt ===\" ||\n  echo \"=== FAILED: $wt ===\"\ndone\n```\n\nThis is slow. Run in background if possible and notify when complete.\n\n## Step 8: Verify and report\n\nAfter setup, verify and report to the user:\n\n```bash\ngit worktree list\n```\n\nSummarize:\n- Number of worktrees created\n- Which env files were copied vs created from defaults vs missing\n- Any warnings or errors encountered\n\n## Final directory layout\n\n```\nparent/\n  main/              # Primary checkout (already exists)\n  reviews/           # PR review worktree\n  branch1/           # Work branch 1\n  branch2/           # Work branch 2\n  ...\n  branchN/           # Work branch N\n```\n",".claude/skills/vercel-react-best-practices/SKILL.md":"---\nname: vercel-react-best-practices\ndescription: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.\nlicense: MIT\nmetadata:\n  author: vercel\n  version: \"1.0.0\"\n---\n\n# Vercel React Best Practices\n\nComprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 45 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.\n\n## When to Apply\n\nReference these guidelines when:\n- Writing new React components or Next.js pages\n- Implementing data fetching (client or server-side)\n- Reviewing code for performance issues\n- Refactoring existing React/Next.js code\n- Optimizing bundle size or load times\n\n## Rule Categories by Priority\n\n| Priority | Category | Impact | Prefix |\n|----------|----------|--------|--------|\n| 1 | Eliminating Waterfalls | CRITICAL | `async-` |\n| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |\n| 3 | Server-Side Performance | HIGH | `server-` |\n| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |\n| 5 | Re-render Optimization | MEDIUM | `rerender-` |\n| 6 | Rendering Performance | MEDIUM | `rendering-` |\n| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |\n| 8 | Advanced Patterns | LOW | `advanced-` |\n\n## Quick Reference\n\n### 1. Eliminating Waterfalls (CRITICAL)\n\n- `async-defer-await` - Move await into branches where actually used\n- `async-parallel` - Use Promise.all() for independent operations\n- `async-dependencies` - Use better-all for partial dependencies\n- `async-api-routes` - Start promises early, await late in API routes\n- `async-suspense-boundaries` - Use Suspense to stream content\n\n### 2. Bundle Size Optimization (CRITICAL)\n\n- `bundle-barrel-imports` - Import directly, avoid barrel files\n- `bundle-dynamic-imports` - Use next/dynamic for heavy components\n- `bundle-defer-third-party` - Load analytics/logging after hydration\n- `bundle-conditional` - Load modules only when feature is activated\n- `bundle-preload` - Preload on hover/focus for perceived speed\n\n### 3. Server-Side Performance (HIGH)\n\n- `server-cache-react` - Use React.cache() for per-request deduplication\n- `server-cache-lru` - Use LRU cache for cross-request caching\n- `server-serialization` - Minimize data passed to client components\n- `server-parallel-fetching` - Restructure components to parallelize fetches\n- `server-after-nonblocking` - Use after() for non-blocking operations\n\n### 4. Client-Side Data Fetching (MEDIUM-HIGH)\n\n- `client-swr-dedup` - Use SWR for automatic request deduplication\n- `client-event-listeners` - Deduplicate global event listeners\n\n### 5. Re-render Optimization (MEDIUM)\n\n- `rerender-defer-reads` - Don't subscribe to state only used in callbacks\n- `rerender-memo` - Extract expensive work into memoized components\n- `rerender-dependencies` - Use primitive dependencies in effects\n- `rerender-derived-state` - Subscribe to derived booleans, not raw values\n- `rerender-functional-setstate` - Use functional setState for stable callbacks\n- `rerender-lazy-state-init` - Pass function to useState for expensive values\n- `rerender-transitions` - Use startTransition for non-urgent updates\n\n### 6. Rendering Performance (MEDIUM)\n\n- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element\n- `rendering-content-visibility` - Use content-visibility for long lists\n- `rendering-hoist-jsx` - Extract static JSX outside components\n- `rendering-svg-precision` - Reduce SVG coordinate precision\n- `rendering-hydration-no-flicker` - Use inline script for client-only data\n- `rendering-activity` - Use Activity component for show/hide\n- `rendering-conditional-render` - Use ternary, not && for conditionals\n\n### 7. JavaScript Performance (LOW-MEDIUM)\n\n- `js-batch-dom-css` - Group CSS changes via classes or cssText\n- `js-index-maps` - Build Map for repeated lookups\n- `js-cache-property-access` - Cache object properties in loops\n- `js-cache-function-results` - Cache function results in module-level Map\n- `js-cache-storage` - Cache localStorage/sessionStorage reads\n- `js-combine-iterations` - Combine multiple filter/map into one loop\n- `js-length-check-first` - Check array length before expensive comparison\n- `js-early-exit` - Return early from functions\n- `js-hoist-regexp` - Hoist RegExp creation outside loops\n- `js-min-max-loop` - Use loop for min/max instead of sort\n- `js-set-map-lookups` - Use Set/Map for O(1) lookups\n- `js-tosorted-immutable` - Use toSorted() for immutability\n\n### 8. Advanced Patterns (LOW)\n\n- `advanced-event-handler-refs` - Store event handlers in refs\n- `advanced-use-latest` - useLatest for stable callback refs\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/async-parallel.md\nrules/bundle-barrel-imports.md\nrules/_sections.md\n```\n\nEach rule file contains:\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n\n## Full Compiled Document\n\nFor the complete guide with all rules expanded: `AGENTS.md`\n",".claude/skills/write-frontend-tests/SKILL.md":"---\nname: write-frontend-tests\ndescription: \"Analyze the current branch diff against dev, plan integration tests for changed frontend pages/components, and write them. TRIGGER when user asks to write frontend tests, add test coverage, or 'write tests for my changes'.\"\nuser-invocable: true\nargs: \"[base branch] — defaults to dev. Optionally pass a specific base branch to diff against.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Write Frontend Tests\n\nAnalyze the current branch's frontend changes, plan integration tests, and write them.\n\n## References\n\nBefore writing any tests, read the testing rules and conventions:\n\n- `autogpt_platform/frontend/TESTING.md` — testing strategy, file locations, examples\n- `autogpt_platform/frontend/src/tests/AGENTS.md` — detailed testing rules, MSW patterns, decision flowchart\n- `autogpt_platform/frontend/src/tests/integrations/test-utils.tsx` — custom render with providers\n- `autogpt_platform/frontend/src/tests/integrations/vitest.setup.tsx` — MSW server setup\n\n## Step 1: Identify changed frontend files\n\n```bash\nBASE_BRANCH=\"${ARGUMENTS:-dev}\"\ncd autogpt_platform/frontend\n\n# Get changed frontend files (excluding generated, config, and test files)\ngit diff \"$BASE_BRANCH\"...HEAD --name-only -- src/ \\\n  | grep -v '__generated__' \\\n  | grep -v '__tests__' \\\n  | grep -v '\\.test\\.' \\\n  | grep -v '\\.stories\\.' \\\n  | grep -v '\\.spec\\.'\n```\n\nAlso read the diff to understand what changed:\n\n```bash\ngit diff \"$BASE_BRANCH\"...HEAD --stat -- src/\ngit diff \"$BASE_BRANCH\"...HEAD -- src/ | head -500\n```\n\n## Step 2: Categorize changes and find test targets\n\nFor each changed file, determine:\n\n1. **Is it a page?** (`page.tsx`) — these are the primary test targets\n2. **Is it a hook?** (`use*.ts`) — test via the page/component that uses it; avoid direct `renderHook()` tests unless it is a shared reusable hook with standalone business logic\n3. **Is it a component?** (`.tsx` in `components/`) — test via the parent page unless it's complex enough to warrant isolation\n4. **Is it a helper?** (`helpers.ts`, `utils.ts`) — unit test directly if pure logic\n\n**Priority order:**\n\n1. Pages with new/changed data fetching or user interactions\n2. Components with complex internal logic (modals, forms, wizards)\n3. Shared hooks with standalone business logic when UI-level coverage is impractical\n4. Pure helper functions\n\nSkip: styling-only changes, type-only changes, config changes.\n\n## Step 3: Check for existing tests\n\nFor each test target, check if tests already exist:\n\n```bash\n# For a page at src/app/(platform)/library/page.tsx\nls src/app/\\(platform\\)/library/__tests__/ 2>/dev/null\n\n# For a component at src/app/(platform)/library/components/AgentCard/AgentCard.tsx\nls src/app/\\(platform\\)/library/components/AgentCard/__tests__/ 2>/dev/null\n```\n\nNote which targets have no tests (need new files) vs which have tests that need updating.\n\n## Step 4: Identify API endpoints used\n\nFor each test target, find which API hooks are used:\n\n```bash\n# Find generated API hook imports in the changed files\ngrep -rn 'from.*__generated__/endpoints' src/app/\\(platform\\)/library/\ngrep -rn 'use[A-Z].*V[12]' src/app/\\(platform\\)/library/\n```\n\nFor each API hook found, locate the corresponding MSW handler:\n\n```bash\n# If the page uses useGetV2ListLibraryAgents, find its MSW handlers\ngrep -rn 'getGetV2ListLibraryAgents.*Handler' src/app/api/__generated__/endpoints/library/library.msw.ts\n```\n\nList every MSW handler you will need (200 for happy path, 4xx for error paths).\n\n## Step 5: Write the test plan\n\nBefore writing code, output a plan as a numbered list:\n\n```\nTest plan for [branch name]:\n\n1. src/app/(platform)/library/__tests__/main.test.tsx (NEW)\n   - Renders page with agent list (MSW 200)\n   - Shows loading state\n   - Shows error state (MSW 422)\n   - Handles empty agent list\n\n2. src/app/(platform)/library/__tests__/search.test.tsx (NEW)\n   - Filters agents by search query\n   - Shows no results message\n   - Clears search\n\n3. src/app/(platform)/library/components/AgentCard/__tests__/AgentCard.test.tsx (UPDATE)\n   - Add test for new \"duplicate\" action\n```\n\nPresent this plan to the user. Wait for confirmation before proceeding. If the user has feedback, adjust the plan.\n\n## Step 6: Write the tests\n\nFor each test file in the plan, follow these conventions:\n\n### File structure\n\n```tsx\nimport { render, screen, waitFor } from \"@/tests/integrations/test-utils\";\nimport { server } from \"@/mocks/mock-server\";\n// Import MSW handlers for endpoints the page uses\nimport {\n  getGetV2ListLibraryAgentsMockHandler200,\n  getGetV2ListLibraryAgentsMockHandler422,\n} from \"@/app/api/__generated__/endpoints/library/library.msw\";\n// Import the component under test\nimport LibraryPage from \"../page\";\n\ndescribe(\"LibraryPage\", () => {\n  test(\"renders agent list from API\", async () => {\n    server.use(getGetV2ListLibraryAgentsMockHandler200());\n\n    render(<LibraryPage />);\n\n    expect(await screen.findByText(/all workflows/i)).toBeDefined();\n  });\n\n  test(\"shows error state on API failure\", async () => {\n    server.use(getGetV2ListLibraryAgentsMockHandler422());\n\n    render(<LibraryPage />);\n\n    expect(await screen.findByText(/error/i)).toBeDefined();\n  });\n});\n```\n\n### Rules\n\n- Use `render()` from `@/tests/integrations/test-utils` (NOT from `@testing-library/react` directly)\n- Use `server.use()` to set up MSW handlers BEFORE rendering\n- Use `findBy*` (async) for elements that appear after data fetching — NOT `getBy*`\n- Use `getBy*` only for elements that are immediately present in the DOM\n- Use `screen` queries — do NOT destructure from `render()`\n- Use `waitFor` when asserting side effects or state changes after interactions\n- Import `fireEvent` or `userEvent` from the test-utils for interactions\n- Do NOT mock internal hooks or functions — mock at the API boundary via MSW\n- Prefer Orval-generated MSW handlers and response builders over hand-built API response objects\n- Do NOT use `act()` manually — `render` and `fireEvent` handle it\n- Keep tests focused: one behavior per test\n- Use descriptive test names that read like sentences\n\n### Test location\n\n```\n# For pages: __tests__/ next to page.tsx\nsrc/app/(platform)/library/__tests__/main.test.tsx\n\n# For complex standalone components: __tests__/ inside component folder\nsrc/app/(platform)/library/components/AgentCard/__tests__/AgentCard.test.tsx\n\n# For pure helpers: co-located .test.ts\nsrc/app/(platform)/library/helpers.test.ts\n```\n\n### Custom MSW overrides\n\nWhen the auto-generated faker data is not enough, override with specific data:\n\n```tsx\nimport { http, HttpResponse } from \"msw\";\n\nserver.use(\n  http.get(\"http://localhost:3000/api/proxy/api/v2/library/agents\", () => {\n    return HttpResponse.json({\n      agents: [{ id: \"1\", name: \"Test Agent\", description: \"A test agent\" }],\n      pagination: { total_items: 1, total_pages: 1, page: 1, page_size: 10 },\n    });\n  }),\n);\n```\n\nUse the proxy URL pattern: `http://localhost:3000/api/proxy/api/v{version}/{path}` — this matches the MSW base URL configured in `orval.config.ts`.\n\n## Step 7: Run and verify\n\nAfter writing all tests:\n\n```bash\ncd autogpt_platform/frontend\npnpm test:unit --reporter=verbose\n```\n\nIf tests fail:\n\n1. Read the error output carefully\n2. Fix the test (not the source code, unless there is a genuine bug)\n3. Re-run until all pass\n\nThen run the full checks:\n\n```bash\npnpm format\npnpm lint\npnpm types\n```\n",".claude/skills/pr-test/SKILL.md":"---\nname: pr-test\ndescription: \"E2E manual testing of PRs/branches using docker compose, agent-browser, and API calls. TRIGGER when user asks to manually test a PR, test a feature end-to-end, or run integration tests against a running system.\"\nuser-invocable: true\nargument-hint: \"[worktree path or PR number] — tests the PR in the given worktree. Optional flags: --fix (auto-fix issues found)\"\nmetadata:\n  author: autogpt-team\n  version: \"2.3.0\"\n---\n\n# Manual E2E Test\n\nTest a PR/branch end-to-end by building the full platform, interacting via browser and API, capturing screenshots, and reporting results.\n\n**Changelog 2.2.0** — auth flow updated for Better Auth (Supabase signup is\ngone), env-setup gaps closed, a proven Playwright fallback for agent-browser,\na billing-test trap that produces false passes, safer process cleanup, and a\nmock-provider pattern for deterministic $0 testing. Learned on\n[#14206](https://github.com/Significant-Gravitas/AutoGPT/pull/14206) — see the\n[evidence comment](https://github.com/Significant-Gravitas/AutoGPT/pull/14206#issuecomment-5511429524).\n**2.2.1** — corrects two claims from 2.2.0 that didn't survive live-stack\nverification (JWKS does **not** rotate on a frontend restart; the local\nPostgres port is `5432`, not `54322`) and hardens the auth setup (explicit\npassword-length/allowlist/rate-limit failure modes, fail-fast on an empty\ntoken, password kept out of process args).\n**2.3.0** — screenshots are posted as GitHub comment attachments\n(`gh pr comment --attach`, needs gh >= 2.99.0) instead of being pushed to a\n`test-screenshots/*` branch, which accumulated one branch per tested PR and\nmade every old report's images depend on that branch surviving.\n\n## Critical Requirements\n\nThese are NON-NEGOTIABLE. Every test run MUST satisfy ALL the following:\n\n### 1. Screenshots at Every Step\n- Take a screenshot at EVERY significant test step — not just at the end\n- Every test scenario MUST have at least one BEFORE and one AFTER screenshot\n- Name screenshots sequentially: `{NN}-{action}-{state}.png` (e.g., `01-credits-before.png`, `02-credits-after.png`)\n- If a screenshot is missing for a scenario, the test is INCOMPLETE — go back and take it\n\n### 2. Screenshots MUST Be Posted to PR\n- Attach ALL screenshots to the PR comment with `gh pr comment --attach` (see Step 7). **Never push image files to a repo branch** — attachments live in GitHub's own asset store and leave nothing behind in the repo\n- Post a PR comment with ALL screenshots embedded inline, each with its explanation\n- This is NOT optional — every test run MUST end with a PR comment containing screenshots\n- If the upload fails, retry. If it still fails, list failed files and require manual drag-and-drop/paste attachment in the PR comment, and mark the run INCOMPLETE\n\n### 3. State Verification with Before/After Evidence\n- For EVERY state-changing operation (API call, user action), capture the state BEFORE and AFTER\n- Log the actual API response values (e.g., `credits_before=100, credits_after=95`)\n- Screenshot MUST show the relevant UI state change\n- Compare expected vs actual values explicitly — do not just eyeball it\n\n### 4. Negative Test Cases Are Mandatory\n- Test at least ONE negative case per feature (e.g., insufficient credits, invalid input, unauthorized access)\n- Verify error messages are user-friendly and accurate\n- Verify the system state did NOT change after a rejected operation\n\n### 5. Test Report Must Include Full Evidence\nEach test scenario in the report MUST have:\n- **Steps**: What was done (exact commands or UI actions)\n- **Expected**: What should happen\n- **Actual**: What actually happened\n- **API Evidence**: Before/after API response values for state-changing operations\n- **Screenshot Evidence**: Before/after screenshots with explanations\n\n## State Manipulation for Realistic Testing\n\n**Billing-test trap — this one produces a false pass, not a visible failure.**\nLLM block cost filters key on the *platform-owned* credential id. A run made\nwith the test user's own API key bills nothing by design, so a credits\nbefore/after assertion silently passes on zero deltas either way. Any test\nthat verifies credit reconciliation MUST use the system credential, not a\nuser-supplied key. Related: Ollama block entries are configured with an\nexplicit `$0` run-based cost (`BlockCostType.RUN, cost_amount=0` in\n`block_cost_config.py`), not a token-metered one — so pre/post-flight cost\ndeltas are always 0 for them regardless of credential. Never use an Ollama\nmodel to test credit reconciliation; pick any hosted model billed through the\nsystem credential instead.\n\nWhen testing features that depend on specific states (rate limits, credits, quotas):\n\n1. **Use Redis CLI to set counters directly:**\n   ```bash\n   # Find the Redis container\n   REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep redis | head -1)\n   # Set a key with expiry\n   docker exec $REDIS_CONTAINER redis-cli SET key value EX ttl\n   # Example: Set rate limit counter to near-limit\n   docker exec $REDIS_CONTAINER redis-cli SET \"rate_limit:user:$PR_TEST_USER_EMAIL\" 99 EX 3600\n   # Example: Check current value\n   docker exec $REDIS_CONTAINER redis-cli GET \"rate_limit:user:$PR_TEST_USER_EMAIL\"\n   ```\n\n2. **Use API calls to check before/after state:**\n   ```bash\n   # BEFORE: Record current state\n   BEFORE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits BEFORE: $BEFORE\"\n\n   # Perform the action...\n\n   # AFTER: Record new state and compare\n   AFTER=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits AFTER: $AFTER\"\n   echo \"Delta: $(( BEFORE - AFTER ))\"\n   ```\n\n3. **Take screenshots BEFORE and AFTER state changes** — the UI must reflect the backend state change\n\n4. **Never rely on mocked/injected browser state** — always use real backend state. Do NOT use `agent-browser eval` to fake UI state. The backend must be the source of truth.\n\n5. **Use direct DB queries when needed:**\n   ```bash\n   # Query via Supabase's PostgREST or docker exec into the DB\n   docker exec supabase-db psql -U supabase_admin -d postgres -c \"SELECT credits FROM user_credits WHERE user_id = '...';\"\n   ```\n\n6. **After every API test, verify the state change actually persisted:**\n   ```bash\n   # Example: After a credits purchase, verify DB matches API\n   API_CREDITS=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   DB_CREDITS=$(docker exec supabase-db psql -U supabase_admin -d postgres -t -c \"SELECT credits FROM user_credits WHERE user_id = '...';\" | tr -d ' ')\n   [ \"$API_CREDITS\" = \"$DB_CREDITS\" ] && echo \"CONSISTENT\" || echo \"MISMATCH: API=$API_CREDITS DB=$DB_CREDITS\"\n   ```\n\n## Arguments\n\n- `$ARGUMENTS` — worktree path (e.g. `$REPO_ROOT`) or PR number\n- If `--fix` flag is present, auto-fix bugs found and push fixes (like pr-address loop)\n\n## Step 0: Resolve the target\n\n```bash\n# If argument is a PR number, find its worktree\ngh pr view {N} --json headRefName --jq '.headRefName'\n# If argument is a path, use it directly\n```\n\nDetermine:\n- `REPO_ROOT` — the root repo directory: `git -C \"$WORKTREE_PATH\" worktree list | head -1 | awk '{print $1}'` (or `git rev-parse --show-toplevel` if not a worktree)\n- `WORKTREE_PATH` — the worktree directory\n- `PLATFORM_DIR` — `$WORKTREE_PATH/autogpt_platform`\n- `BACKEND_DIR` — `$PLATFORM_DIR/backend`\n- `FRONTEND_DIR` — `$PLATFORM_DIR/frontend`\n- `PR_NUMBER` — the PR number (from `gh pr list --head $(git branch --show-current)`)\n- `PR_TITLE` — the PR title, slugified (e.g. \"Add copilot permissions\" → \"add-copilot-permissions\")\n- `RESULTS_DIR` — `$REPO_ROOT/test-results/PR-{PR_NUMBER}-{slugified-title}`\n\nCreate the results directory:\n```bash\nPR_NUMBER=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')\nPR_TITLE=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json title --jq '.[0].title' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | head -c 50)\nRESULTS_DIR=\"$REPO_ROOT/test-results/PR-${PR_NUMBER}-${PR_TITLE}\"\nmkdir -p $RESULTS_DIR\n```\n\n**Test user credentials** — required to log into the UI or call authenticated APIs.\n\nNEVER hardcode these in this SKILL, a PR comment, a screenshot, or any committed file. Sources, in priority order:\n\n1. **Env vars** (CI / preconfigured local shell): `$PR_TEST_USER_EMAIL` + `$PR_TEST_USER_PASSWORD`. If both are set, use them.\n2. **Interactive prompt** (everything else, including dev-preview runs): if the env vars are not set, ASK the user at the start of the run — e.g. \"I need test-user credentials for this run; paste the email and password.\" Hold them in shell vars only for the duration of the run. Do not echo, log, or write them to disk.\n\nAcquire the variables — env first, prompt if missing — and only then lock them in:\n\n```bash\n# 1. Prefer env vars (CI / preconfigured shell). Prompt only for the\n#    specific var that is unset so an already-exported credential is\n#    not overwritten by the prompt when only the other one is missing.\nif [ -z \"${PR_TEST_USER_EMAIL:-}\" ] || [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n  echo \"Test user credentials required for this run.\"\n  if [ -z \"${PR_TEST_USER_EMAIL:-}\" ]; then\n    read -r -p   \"Email:    \" PR_TEST_USER_EMAIL\n  fi\n  if [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n    read -r -s -p \"Password: \" PR_TEST_USER_PASSWORD\n    echo\n  fi\n  export PR_TEST_USER_EMAIL PR_TEST_USER_PASSWORD\nfi\n\n# 2. Lock them in — fail loudly if either is STILL unset (e.g. the user\n#    pressed Enter on an empty prompt). The error message names the var so\n#    the agent / operator knows what to fix.\n: \"${PR_TEST_USER_EMAIL:?PR_TEST_USER_EMAIL is empty after env+prompt — supply a value before re-running}\"\n: \"${PR_TEST_USER_PASSWORD:?PR_TEST_USER_PASSWORD is empty after env+prompt — supply a value before re-running}\"\n```\n\nFor **local docker-compose** runs, a fresh dev user is created on first call to the signup snippet below. For **dev-preview** runs, the test user lives in the project's hosted auth backend — ask the user for the current valid credentials each session (the previously-shared `test@test.com` test account was disabled on 2026-05-23 after its credentials leaked into this very SKILL — do NOT re-introduce a default). **`PR_TEST_USER_PASSWORD` should always be a throwaway/test-only credential, never a real account's password** — the auth requests in 3h go over plain HTTP on `localhost:3000` for local runs, which has no transport encryption. If a dev-preview run's target isn't on `localhost`, confirm it's HTTPS before sending credentials to it.\n\n## Step 1: Understand the PR\n\nBefore testing, understand what changed:\n\n```bash\ncd $WORKTREE_PATH\n\n# Read PR description to understand the WHY\ngh pr view {N} --json body --jq '.body'\n\ngit log --oneline dev..HEAD | head -20\ngit diff dev --stat\n```\n\nRead the PR description (Why / What / How) and changed files to understand:\n0. **Why** does this PR exist? What problem does it solve?\n1. **What** feature/fix does this PR implement?\n2. **How** does it work? What's the approach?\n3. What components are affected? (backend, frontend, copilot, executor, etc.)\n4. What are the key user-facing behaviors to test?\n\n## Step 2: Write test scenarios\n\nBased on the PR analysis, write a test plan to `$RESULTS_DIR/test-plan.md`:\n\n```markdown\n# Test Plan: PR #{N} — {title}\n\n## Scenarios\n1. [Scenario name] — [what to verify]\n2. ...\n\n## API Tests (if applicable)\n1. [Endpoint] — [expected behavior]\n   - Before state: [what to check before]\n   - After state: [what to verify changed]\n\n## UI Tests (if applicable)\n1. [Page/component] — [interaction to test]\n   - Screenshot before: [what to capture]\n   - Screenshot after: [what to capture]\n\n## Negative Tests (REQUIRED — at least one per feature)\n1. [What should NOT happen] — [how to trigger it]\n   - Expected error: [what error message/code]\n   - State unchanged: [what to verify did NOT change]\n```\n\n**Be critical** — include edge cases, error paths, and security checks. Every scenario MUST specify what screenshots to take and what state to verify.\n\n## Step 3.0: Claim the testing lock (coordinate parallel agents)\n\nMultiple worktrees share the same host — Docker infra (postgres, redis, clamav), app ports (3000/8006/…), and the test user. Two agents running `/pr-test` concurrently will corrupt each other's state (connection-pool exhaustion, port binds failing silently, cross-test assertions). Use the root-worktree lock file to take turns.\n\n### Lock file contract\n\nPath (**always** the root worktree so all siblings see it): `$REPO_ROOT/.ign.testing.lock`\n\nBody (one `key=value` per line):\n```\nholder=<pr-XXXXX-purpose>\npid=<pid-or-\"self\">\nstarted=<iso8601>\nheartbeat=<iso8601, updated every ~2 min>\nworktree=<full path>\nbranch=<branch name>\nintent=<one-line description + rough duration>\n```\n\n### Claim\n\n```bash\nLOCK=$REPO_ROOT/.ign.testing.lock\nNOW=$(date -u +%Y-%m-%dT%H:%MZ)\nSTALE_AFTER_MIN=5\n\nif [ -f \"$LOCK\" ]; then\n  HB=$(grep '^heartbeat=' \"$LOCK\" | cut -d= -f2)\n  HB_EPOCH=$(date -j -f '%Y-%m-%dT%H:%MZ' \"$HB\" +%s 2>/dev/null || date -d \"$HB\" +%s 2>/dev/null || echo 0)\n  AGE_MIN=$(( ( $(date -u +%s) - HB_EPOCH ) / 60 ))\n  if [ \"$AGE_MIN\" -gt \"$STALE_AFTER_MIN\" ]; then\n    echo \"WARN: stale lock (${AGE_MIN}m old) — reclaiming\"\n    cat \"$LOCK\" | sed 's/^/  stale: /'\n  else\n    echo \"Another agent holds the lock:\"; cat \"$LOCK\"\n    echo \"Wait until released or resume after $((STALE_AFTER_MIN - AGE_MIN))m.\"\n    exit 1\n  fi\nfi\n\ncat > \"$LOCK\" <<EOF\nholder=pr-${PR_NUMBER}-e2e\npid=self\nstarted=$NOW\nheartbeat=$NOW\nworktree=$WORKTREE_PATH\nbranch=$(cd $WORKTREE_PATH && git branch --show-current)\nintent=E2E test PR #${PR_NUMBER}, native mode, ~60min\nEOF\necho \"Lock claimed\"\n```\n\n### Heartbeat (MUST run in background during the whole test)\n\nWithout a heartbeat a crashed agent keeps the lock forever. Run this as a background process right after claim:\n\n```bash\n(while true; do\n   sleep 120\n   [ -f \"$LOCK\" ] || exit 0   # lock released → exit heartbeat\n   perl -i -pe \"s/^heartbeat=.*/heartbeat=$(date -u +%Y-%m-%dT%H:%MZ)/\" \"$LOCK\"\n done) &\nHEARTBEAT_PID=$!\necho \"$HEARTBEAT_PID\" > /tmp/pr-test-heartbeat.pid\n```\n\n### Release (always — even on failure)\n\n```bash\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\nUse a `trap` so release runs even on `exit 1`:\n```bash\ntrap 'kill \"$HEARTBEAT_PID\" 2>/dev/null; rm -f \"$LOCK\"' EXIT INT TERM\n```\n\n### **Release the lock AS SOON AS the test run is done**\n\nThe lock guards **test execution**, not **app lifecycle**. Once Step 5 (record results) and Step 6 (post PR comment) are complete, release the lock IMMEDIATELY — even if:\n\n- The native `poetry run app` / `pnpm dev` processes are still running so the user can keep poking at the app manually.\n- You're leaving docker containers up.\n- You're tailing logs for a minute or two.\n\nKeeping the lock held past the test run is the single most common way `/pr-test` stalls other agents. **The app staying up is orthogonal to the lock; don't conflate them.** Sibling worktrees running their own `/pr-test` will kill the stray processes and free the ports themselves (Step 3c/3e-native handle that) — they just need the lock file gone.\n\nConcretely, the sequence at the end of every `/pr-test` run (success or failure) is:\n\n```bash\n# 1. Write the final report + post PR comment — done above in Step 5/6.\n# 2. Release the lock right now, even if the app is still up.\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock (app may still be running)\" \\\n    >> $REPO_ROOT/.ign.testing.log\n# 3. Optionally leave the app running and note it so the user knows:\necho \"Native stack still running on :3000 / :8006 for manual poking. Kill with:\"\necho \"  pkill -9 -f 'poetry run app'; pkill -9 -f 'next-server|next dev'\"\n```\n\nIf a sibling agent's `/pr-test` needs to take over, it'll do the kill+rebuild dance from Step 3c/3e-native on its own — your only job is to not hold the lock file past the end of your test.\n\n### Shared status log\n\n`$REPO_ROOT/.ign.testing.log` is an append-only channel any agent can read/write. Use it for \"I'm waiting\", \"I'm done, resources free\", or post-run notes:\n```bash\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] <message>\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\n## Step 3: Environment setup\n\n### 3a. Copy .env files from the root worktree\n\nThe root worktree (`$REPO_ROOT`) has the canonical `.env` files with all API keys. Copy them to the target worktree:\n\n```bash\n# CRITICAL: .env files are NOT checked into git. They must be copied manually.\ncp $REPO_ROOT/autogpt_platform/.env $PLATFORM_DIR/.env\ncp $REPO_ROOT/autogpt_platform/backend/.env $BACKEND_DIR/.env\ncp $REPO_ROOT/autogpt_platform/frontend/.env $FRONTEND_DIR/.env\n```\n\n**A copy from the root worktree is no longer sufficient on a recent `dev`** —\nauth moved from Supabase to Better Auth (see 3h) and two vars are easy to\nmiss because nothing fails loudly without them, it just 401s later:\n\n- `$BACKEND_DIR/.env` needs `JWT_JWKS_URL` — the Better Auth JWKS endpoint the\n  backend verifies tokens against. The `localhost:3000` value below is for\n  **native mode only**. In docker mode it's harmless to have it in `.env`\n  because `docker-compose.platform.yml` overrides it with the\n  Compose-reachable `http://frontend:3000/api/auth/jwks` — but if you ever run\n  the backend against this `.env` value directly (bypassing Compose), a\n  `localhost` value inside a container resolves to itself, not the frontend,\n  and every call 401s with no other symptom.\n- `$FRONTEND_DIR/.env` needs its **own** `DATABASE_URL` — Better Auth runs\n  inside the Next.js app and talks to Postgres directly, it does not go\n  through the backend. **Derive it from `$BACKEND_DIR/.env`'s `DB_USER` /\n  `DB_PASS` / `DB_PORT` / `DB_NAME` rather than copying\n  `frontend/.env.default`'s placeholder verbatim** — the placeholder happens\n  to match the stock local defaults, but if `backend/.env`'s `DB_PASS` was\n  ever customized (rotated secret, non-default port), copying the placeholder\n  silently points Better Auth at the wrong database instead of the one the\n  rest of the stack actually uses.\n\n```bash\n# [ -n ... ], not grep -q alone — a present-but-empty JWT_JWKS_URL= would\n# otherwise be treated as \"already set\" and skip the fallback, leaving the\n# backend without a JWKS endpoint to verify tokens against.\n[ -n \"$(grep '^JWT_JWKS_URL=' $BACKEND_DIR/.env | cut -d= -f2-)\" ] || echo \"JWT_JWKS_URL=http://localhost:3000/api/auth/jwks\" >> $BACKEND_DIR/.env  # native mode only — see note above\n\nif [ -n \"$(grep '^DATABASE_URL=' $FRONTEND_DIR/.env | cut -d= -f2-)\" ]; then\n  # grep -q alone matches a present-but-empty DATABASE_URL= too, which would\n  # otherwise skip derivation and leave Better Auth pointed at nothing.\n  echo \"Frontend DATABASE_URL: already set (not touching it)\"\nelse\n  # cut -f2 (not -f2-) truncates any value containing '=' (base64 secrets do); -f2- keeps the rest.\n  DB_USER=$(grep '^DB_USER=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PASS=$(grep '^DB_PASS=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PORT=$(grep '^DB_PORT=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_NAME=$(grep '^DB_NAME=' $BACKEND_DIR/.env | cut -d= -f2-)\n  : \"${DB_USER:?}\" \"${DB_PASS:?}\" \"${DB_PORT:?}\" \"${DB_NAME:?}\"  # fail loudly, not with a silently-empty URL\n  # Percent-encode user/pass — a raw '@', '#', '?', '%', or ':' in either would\n  # otherwise be misparsed as URL structure instead of credential content.\n  # Via env vars, not `jq --arg`, which would put DB_PASS in the process arglist.\n  DB_USER_ENC=$(DB_USER_VAL=\"$DB_USER\" jq -rn '$ENV.DB_USER_VAL|@uri')\n  DB_PASS_ENC=$(DB_PASS_VAL=\"$DB_PASS\" jq -rn '$ENV.DB_PASS_VAL|@uri')\n  echo \"DATABASE_URL=postgresql://${DB_USER_ENC}:${DB_PASS_ENC}@localhost:${DB_PORT}/${DB_NAME}\" >> $FRONTEND_DIR/.env\n  # Reconstructed, not regex-redacted — a password containing '@' would otherwise\n  # leak its tail past a naive \"redact up to the first @\" pattern.\n  echo \"Frontend DATABASE_URL: postgresql://${DB_USER_ENC}:***@localhost:${DB_PORT}/${DB_NAME}\"\nfi\n```\n\n### 3b. Configure copilot authentication\n\nThe copilot needs an LLM API to function. Two approaches (try subscription first):\n\n#### Option 1: Subscription mode (preferred — uses your Claude Max/Pro subscription)\n\nThe `claude_agent_sdk` Python package **bundles its own Claude CLI binary** — no need to install `@anthropic-ai/claude-code` via npm. The backend auto-provisions credentials from environment variables on startup.\n\nRun the helper script to extract tokens from your host and auto-update `backend/.env` (works on macOS, Linux, and Windows/WSL):\n\n```bash\n# Extracts OAuth tokens and writes CLAUDE_CODE_OAUTH_TOKEN + CLAUDE_CODE_REFRESH_TOKEN into .env\nbash $BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env\n```\n\n**How it works:** The script reads the OAuth token from:\n- **macOS**: system keychain (`\"Claude Code-credentials\"`)\n- **Linux/WSL**: `~/.claude/.credentials.json`\n- **Windows**: `%APPDATA%/claude/.credentials.json`\n\nIt sets `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REFRESH_TOKEN`, and `CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true` in the `.env` file. On container startup, the backend auto-provisions `~/.claude/.credentials.json` inside the container from these env vars. The SDK's bundled CLI then authenticates using that file. No `claude login`, no npm install needed.\n\n**Note:** The OAuth token expires (~24h). If copilot returns auth errors, re-run the script and restart: `$BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env && docker compose up -d copilot_executor`\n\n#### Option 2: OpenRouter API key mode (fallback)\n\nIf subscription mode doesn't work, switch to API key mode using OpenRouter:\n\n```bash\n# In $BACKEND_DIR/.env, ensure these are set:\nCHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false\nCHAT_API_KEY=<value of OPEN_ROUTER_API_KEY from the same .env>\nCHAT_BASE_URL=https://openrouter.ai/api/v1\nCHAT_USE_CLAUDE_AGENT_SDK=true\n```\n\nUse `sed` to update these values:\n```bash\nORKEY=$(grep \"^OPEN_ROUTER_API_KEY=\" $BACKEND_DIR/.env | cut -d= -f2)\n[ -n \"$ORKEY\" ] || { echo \"ERROR: OPEN_ROUTER_API_KEY is missing in $BACKEND_DIR/.env\"; exit 1; }\nperl -i -pe 's/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false/' $BACKEND_DIR/.env\n# Add or update CHAT_API_KEY and CHAT_BASE_URL\ngrep -q \"^CHAT_API_KEY=\" $BACKEND_DIR/.env && perl -i -pe \"s|^CHAT_API_KEY=.*|CHAT_API_KEY=$ORKEY|\" $BACKEND_DIR/.env || echo \"CHAT_API_KEY=$ORKEY\" >> $BACKEND_DIR/.env\ngrep -q \"^CHAT_BASE_URL=\" $BACKEND_DIR/.env && perl -i -pe 's|^CHAT_BASE_URL=.*|CHAT_BASE_URL=https://openrouter.ai/api/v1|' $BACKEND_DIR/.env || echo \"CHAT_BASE_URL=https://openrouter.ai/api/v1\" >> $BACKEND_DIR/.env\n```\n\n### 3c. Stop conflicting containers\n\n```bash\n# Stop any running app containers (keep infra: supabase, redis, rabbitmq, clamav)\ndocker ps --format \"{{.Names}}\" | grep -E \"rest_server|executor|copilot|websocket|database_manager|scheduler|notification|frontend|migrate\" | while read name; do\n  docker stop \"$name\" 2>/dev/null\ndone\n```\n\n**Native mode also:** when running the app natively (see 3e-native), kill any stray host processes and free the app ports before starting — otherwise `poetry run app` and `pnpm dev` will fail to bind.\n\n**Kill by port, not by broad process pattern.** A pattern-based\n`pkill -f \"python.*backend\"` (or anything matching by worktree cwd) is too\ncoarse on a host running several worktrees — it has taken out the frontend\nand a mock server sitting on other ports along with the intended backend\nprocess. Target the pid actually holding each port instead — this only kills\nwhoever is bound to that specific port, which is narrower than a pattern\nmatch, but **it is not worktree isolation**: if a sibling worktree's own dev\nserver happens to be using one of these ports (e.g. its frontend also on\n:3000), this kills that too. `lsof` tells you who holds the port, not who\nowns it — a `docker-proxy` pid there means a compose stack owns it.\n\n```bash\n# Free app ports one at a time — errors per port are ignored (port may simply\n# be unused). `xargs -r` is GNU-only (macOS xargs rejects -r); the `[ -n ]`\n# guard below is the portable equivalent.\nfor port in 3000 8006 8001 8002 8005 8008; do\n  pids=$(lsof -ti :$port -sTCP:LISTEN 2>/dev/null)\n  [ -n \"$pids\" ] && kill -9 $pids 2>/dev/null || true\ndone\n```\n\n### 3e-native. Run the app natively (PREFERRED for iterative dev)\n\nNative mode runs infra (postgres, supabase, redis, rabbitmq, clamav) in docker but runs the backend and frontend directly on the host. This avoids the 3-8 minute `docker compose build` cycle on every backend change — code edits are picked up on process restart (seconds) instead of a full image rebuild.\n\n**When to prefer native mode (default for this skill):**\n- Iterative dev/debug loops where you're editing backend or frontend code between test runs\n- Any PR that touches Python/TS source but not Dockerfiles, compose config, or infra images\n- Fast repro of a failing scenario — restart `poetry run app` in a couple of seconds\n\n**When to prefer docker mode (3e fallback):**\n- Testing changes to `Dockerfile`, `docker-compose.yml`, or base images\n- Production-parity smoke tests (exact container env, networking, volumes)\n- CI-equivalent runs where you need the exact image that'll ship\n\n**Note on 3b (copilot auth):** no npm install anywhere. `poetry install` pulls in `claude_agent_sdk`, which ships its own Claude CLI binary — available on `PATH` whenever you run commands via `poetry run` (native) OR whenever the copilot_executor container is built from its Poetry lockfile (docker). The OAuth token extraction still applies (same `refresh_claude_token.sh` call).\n\n**Preamble:** before starting native, run the kill-stray + free-ports block from 3c's \"Native mode also\" subsection.\n\n**1. Start infra only (one-time per session):**\n\n```bash\ncd $PLATFORM_DIR && docker compose --profile local up deps --detach --remove-orphans --build\n```\n\nThis brings up postgres/supabase/redis/rabbitmq/clamav and skips all app services.\n\n**2. Start the backend natively:**\n\n```bash\ncd $BACKEND_DIR && (poetry run app 2>&1 | tee .ign.application.logs) &\n```\n\n`poetry run app` spawns **all** app subprocesses — `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, `database_manager` — inside ONE parent process. No separate containers, no separate terminals. The `.ign.application.logs` prefix is already gitignored.\n\n**3. Wait for the backend on :8006 BEFORE starting the frontend.** This ordering matters — the frontend's `pnpm dev` startup invokes `generate-api-queries`, which fetches `/openapi.json` from the backend. If the backend isn't listening yet, `pnpm dev` fails immediately.\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8006/docs 2>/dev/null)\" = \"200\" ]; then\n    echo \"Backend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\n**4. Start the frontend natively:**\n\n```bash\ncd $FRONTEND_DIR && (pnpm dev 2>&1 | tee .ign.frontend.logs) &\n```\n\n**5. Wait for the frontend on :3000:**\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000 2>/dev/null)\" = \"200\" ]; then\n    echo \"Frontend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\nOnce both are up, skip 3e/3f and go straight to **3g/3h** (feature flags / test user creation).\n\n### 3e. Build and start (docker — fallback)\n\n```bash\ncd $PLATFORM_DIR && docker compose build --no-cache 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker build failed\"; exit 1; fi\n\ncd $PLATFORM_DIR && docker compose up -d 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker compose up failed\"; exit 1; fi\n```\n\n**Note:** If the container appears to be running old code (e.g. missing PR changes), use `docker compose build --no-cache` to force a full rebuild. Docker BuildKit may sometimes reuse cached `COPY` layers from a previous build on a different branch.\n\n**Expected time: 3-8 minutes** for build, 5-10 minutes with `--no-cache`.\n\n### 3f. Wait for services to be ready\n\n```bash\n# Poll until backend and frontend respond\nfor i in $(seq 1 60); do\n  BACKEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8006/docs 2>/dev/null)\n  FRONTEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000 2>/dev/null)\n  if [ \"$BACKEND\" = \"200\" ] && [ \"$FRONTEND\" = \"200\" ]; then\n    echo \"Services ready\"\n    break\n  fi\n  sleep 5\ndone\n```\n\n\n### 3h. Create test user and get auth token\n\nThe platform moved off Supabase auth to Better Auth, embedded in the\nNext.js app at `/api/auth/*`. Signup and sign-in both go through the frontend\nnow, not Kong on :8000 — and `/api/auth/token` mints a backend-API JWT from a\n**session cookie**, it does not accept credentials directly, so sign-in has to\nhappen first to get that cookie.\n\nBetter Auth's default minimum password length is **12 characters** — shorter\nvalues fail signup with `PASSWORD_TOO_SHORT` and every step below degrades\nsilently into an empty token unless you check for it.\n\n```bash\nCOOKIE_JAR=$(mktemp)\ntrap 'rm -f \"$COOKIE_JAR\"' EXIT  # cleans up on early exit too, not just the happy path\n# Via env vars, not `jq --arg`, which would put the password in the process arglist.\nAUTH_PAYLOAD=$(PR_TEST_USER_EMAIL=\"$PR_TEST_USER_EMAIL\" PR_TEST_USER_PASSWORD=\"$PR_TEST_USER_PASSWORD\" \\\n  jq -nc '{email:$ENV.PR_TEST_USER_EMAIL,password:$ENV.PR_TEST_USER_PASSWORD,name:\"PR Test User\"}')\n\n# Signup (idempotent — a real error body means \"already exists\" only if you\n# check; -d passes the payload as an argument, which leaks the password into\n# process listings, so pipe it through stdin with --data-binary @- instead.\n# --noproxy guards against an inherited proxy env var routing the password\n# through a proxy. --max-time bounds it — without one, a server that accepts\n# the connection but never responds hangs setup indefinitely instead of\n# reaching the empty-$TOKEN failure check below).\nSIGNUP_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -X POST 'http://localhost:3000/api/auth/sign-up/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNUP_RESULT\" | grep -qi '\"code\"' && echo \"Signup: $SIGNUP_RESULT\"  # log it — \"already exists\" and \"password too short\" look identical downstream otherwise\n\n# Sign in — sets the better-auth.session_token cookie in $COOKIE_JAR.\n# Capture the body: a failure here (e.g. account exists with a different\n# password) otherwise only shows up as an empty $TOKEN with no explanation.\nSIGNIN_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -c \"$COOKIE_JAR\" -X POST 'http://localhost:3000/api/auth/sign-in/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNIN_RESULT\" | grep -qi '\"code\"' && echo \"Sign-in: $SIGNIN_RESULT\"\n\n# Mint a backend-API JWT from the session cookie\nTOKEN=$(curl -s -b \"$COOKIE_JAR\" 'http://localhost:3000/api/auth/token' | jq -r '.token // \"\"')\n[ -n \"$TOKEN\" ] || { echo \"ERROR: auth setup failed — TOKEN is empty. Check password length (min 12 chars), AUTH_ALLOW_NEW_ACCOUNTS, AUTH_SIGNUP_ALLOWLIST, and rate limiting on /api/auth/*.\"; exit 1; }\n```\n\n**Use this token for ALL API calls:**\n```bash\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/...\n```\n\n### 3i. Disable onboarding for test user\n\nThe frontend redirects to `/onboarding` when the `ONBOARDING_COMPLETE` step is not in `completedSteps`.\nMark it complete via the backend API so every browser test lands on the real feature UI:\n\n```bash\nONBOARDING_RESULT=$(curl -s --max-time 30 -X POST \\\n  \"http://localhost:8006/api/onboarding/step?step=ONBOARDING_COMPLETE\" \\\n  -H \"Authorization: Bearer $TOKEN\")\necho \"Onboarding bypass: $ONBOARDING_RESULT\"\n\n# Verify it took effect\nONBOARDING_STATUS=$(curl -s --max-time 30 \\\n  \"http://localhost:8006/api/onboarding/completed\" \\\n  -H \"Authorization: Bearer $TOKEN\" | jq -r '.is_completed')\necho \"Onboarding completed: $ONBOARDING_STATUS\"\nif [ \"$ONBOARDING_STATUS\" != \"true\" ]; then\n  echo \"ERROR: onboarding bypass failed — browser tests will hit /onboarding instead of the target feature. Investigate before proceeding.\"\n  exit 1\nfi\n```\n\n## Step 4: Run tests\n\n### Service ports reference\n\n| Service | Port | URL |\n|---------|------|-----|\n| Frontend | 3000 | http://localhost:3000 |\n| Backend REST | 8006 | http://localhost:8006 |\n| Supabase Auth (via Kong) | 8000 | http://localhost:8000 |\n| Executor (metrics only) | 8002 | http://localhost:8002 in native mode; not published by Docker Compose |\n| Copilot Executor (metrics only) | 8008 | http://localhost:8008 in native mode; not published by Docker Compose |\n| WebSocket | 8001 | http://localhost:8001 |\n| Database Manager | 8005 | http://localhost:8005 in native mode; not published by Docker Compose. Every method needs `Authorization: Bearer <internal key>` (`backend/util/service_auth.py`) |\n| Redis | 6379 | localhost:6379 |\n| RabbitMQ | 5672 | localhost:5672 |\n\n### API testing\n\nUse `curl` with the auth token for backend API tests. **For EVERY API call that changes state, record before/after values:**\n\n```bash\n# Example: List agents\ncurl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/graphs | jq . | head -20\n\n# Example: Create an agent\ncurl -s -X POST http://localhost:8006/api/graphs \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{...}' | jq .\n\n# Example: Run an agent\ncurl -s -X POST \"http://localhost:8006/api/graphs/{graph_id}/execute\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"data\": {...}}'\n\n# Example: Get execution results\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:8006/api/graphs/{graph_id}/executions/{exec_id}\" | jq .\n```\n\n**State verification pattern (use for EVERY state-changing API call):**\n```bash\n# 1. Record BEFORE state\nBEFORE_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"BEFORE: $BEFORE_STATE\"\n\n# 2. Perform the action\nACTION_RESULT=$(curl -s -X POST ... | jq .)\necho \"ACTION RESULT: $ACTION_RESULT\"\n\n# 3. Record AFTER state\nAFTER_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"AFTER: $AFTER_STATE\"\n\n# 4. Log the comparison\necho \"=== STATE CHANGE VERIFICATION ===\"\necho \"Before: $BEFORE_STATE\"\necho \"After: $AFTER_STATE\"\necho \"Expected change: {describe what should have changed}\"\n```\n\n### Mock-provider pattern (deterministic, $0)\n\nFor timeout/latency/error-handling behavior that would otherwise need a real\nLLM call, point the OpenAI SDK at a local mock instead — it honours\n`OPENAI_BASE_URL`, so a small local Responses-API server can stand in for the\nprovider while everything downstream (executor, credentials, billing) still\nruns for real. This proved out 4/4 test items at $0 in this run. **This\ncovers LLM blocks (`providers.py`) and the Codex block** — the copilot's own\nLLM calls go through `backend/util/clients.py`, which passes `base_url`\nexplicitly and does not read `OPENAI_BASE_URL`, so this trick doesn't reach\ncopilot chat.\n\n```bash\n# In $BACKEND_DIR/.env, point the OpenAI provider at a local mock server\n# that implements the subset of the Responses API you need (e.g. delayed\n# responses to test timeout handling, or a 500 to test error surfacing).\n# Restart the backend after changing this — it's read at startup.\nOPENAI_BASE_URL=http://localhost:{mock_port}/v1\n```\n\n**Use a throwaway/dummy provider credential with the mock, never the system\ncredential** — only the transport is faked, so the credential lookup still\nruns for real and whatever key you configure gets sent as a header to your\nlocal mock server. A dummy key also means the mock server's logs (which may\nend up pasted into a PR comment) can't leak a real one. Aside from the\ncredential, this is safe to use for anything that isn't itself testing model\n*output* quality.\n\nIn **docker mode**, `localhost` inside the backend container isn't reachable\nfrom your host-side mock server — point `OPENAI_BASE_URL` at a\nCompose-reachable hostname instead (or `host.docker.internal` with a\n`host-gateway` entry), and set it before `docker compose up` or restart the\naffected services after changing `$BACKEND_DIR/.env`.\n\nKeep the mock server's own response timeout short — the OpenAI SDK's default\nclient timeout is 600s, so a hung mock stalls every LLM-backed block for that\nlong instead of failing fast.\n\n### Browser testing with agent-browser\n\nPrimary tool — use this wherever `agent-browser` is installed:\n\n```bash\n# Close any existing session\nagent-browser close 2>/dev/null || true\n\n# Use --session-name to persist cookies across navigations\n# This means login only needs to happen once per test session\nagent-browser --session-name pr-test open 'http://localhost:3000/login' --timeout 15000\n\n# Get interactive elements\nagent-browser --session-name pr-test snapshot | grep \"textbox\\|button\"\n\n# Login (read creds from env — set PR_TEST_USER_EMAIL / PR_TEST_USER_PASSWORD or ask the user)\nagent-browser --session-name pr-test fill {email_ref} \"$PR_TEST_USER_EMAIL\"\nagent-browser --session-name pr-test fill {password_ref} \"$PR_TEST_USER_PASSWORD\"\nagent-browser --session-name pr-test click {login_button_ref}\nsleep 5\n\n# Dismiss cookie banner if present\nagent-browser --session-name pr-test click 'text=Accept All' 2>/dev/null || true\n\n# Navigate — cookies are preserved so login persists\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n\n# Take screenshot\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/01-page.png\n\n# Interact with elements\nagent-browser --session-name pr-test fill {ref} \"text\"\nagent-browser --session-name pr-test press \"Enter\"\nagent-browser --session-name pr-test click {ref}\nagent-browser --session-name pr-test click 'text=Button Text'\n\n# Read page content\nagent-browser --session-name pr-test snapshot | grep \"text:\"\n```\n\n**Key pages:**\n- `/copilot` — CoPilot chat (for testing copilot features)\n- `/build` — Agent builder (for testing block/node features)\n- `/build?flowID={id}` — Specific agent in builder\n- `/library` — Agent library (for testing listing/import features)\n- `/library/agents/{id}` — Agent detail with run history\n- `/marketplace` — Marketplace\n\n**Fallback — if `agent-browser` isn't installed on the host, don't let `npx`\ndownload it.** Use `@playwright/test` instead — the package is already in the\nfrontend's `node_modules`, but **the Chromium binary itself is not**;\nPlaywright caches browser binaries separately (`~/.cache/ms-playwright/` by\ndefault) and nothing installs them automatically. Run\n`pnpm exec playwright install chromium` once per host if `chromium.launch()`\nfails looking for an executable, then use it for finer control over timing\n(`waitForSelector`, explicit timeouts) than agent-browser's CLI gives you:\n\n```bash\ncd $FRONTEND_DIR  # required — @playwright/test is only declared here, not at the repo root\n# Check the installer's own exit status, not grep's — piping through grep to\n# drop blank lines would otherwise swallow a real install failure and let\n# chromium.launch() below fail later with a much less clear error.\npnpm exec playwright install chromium\n[ $? -eq 0 ] || { echo \"ERROR: playwright install chromium failed\"; exit 1; }\n\nnode -e \"\nconst { chromium } = require('@playwright/test');\n(async () => {\n  const browser = await chromium.launch();\n  try {\n    const page = await browser.newPage();\n    await page.goto('http://localhost:3000/login');\n    await page.screenshot({ path: '$RESULTS_DIR/01-login.png' });\n  } finally {\n    await browser.close();  // otherwise a goto timeout leaks a headless Chromium process\n  }\n})();\n\"\n```\n\n### Checking logs\n\n**Native mode:** when running via `poetry run app` + `pnpm dev`, all app logs stream to the `.ign.*.logs` files written by the `tee` pipes in 3e-native. `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, and `database_manager` are all subprocesses of the single `poetry run app` parent, so their output is interleaved in `.ign.application.logs`.\n\n```bash\n# Backend (all app subprocesses interleaved)\ntail -f $BACKEND_DIR/.ign.application.logs\n\n# Frontend (Next.js dev server)\ntail -f $FRONTEND_DIR/.ign.frontend.logs\n\n# Filter for errors across either log\ngrep -iE \"error|exception|traceback\" $BACKEND_DIR/.ign.application.logs | tail -20\ngrep -iE \"error|exception|traceback\" $FRONTEND_DIR/.ign.frontend.logs | tail -20\n```\n\n**Docker mode:**\n\n```bash\n# Backend REST server\ndocker logs autogpt_platform-rest_server-1 2>&1 | tail -30\n\n# Executor (runs agent graphs)\ndocker logs autogpt_platform-executor-1 2>&1 | tail -30\n\n# Copilot executor (runs copilot chat sessions)\ndocker logs autogpt_platform-copilot_executor-1 2>&1 | tail -30\n\n# Frontend\ndocker logs autogpt_platform-frontend-1 2>&1 | tail -30\n\n# Filter for errors\ndocker logs autogpt_platform-executor-1 2>&1 | grep -i \"error\\|exception\\|traceback\" | tail -20\n```\n\n### Copilot chat testing\n\nThe copilot uses SSE streaming. To test via API:\n\n```bash\n# Create a session\nSESSION_ID=$(curl -s -X POST 'http://localhost:8006/api/chat/sessions' \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}' | jq -r '.id // .session_id // \"\"')\n\n# Stream a message (SSE - will stream chunks)\ncurl -N -X POST \"http://localhost:8006/api/chat/sessions/$SESSION_ID/stream\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"message\": \"Hello, what can you help me with?\"}' \\\n  --max-time 60 2>/dev/null | head -50\n```\n\nOr test via browser (preferred for UI verification):\n```bash\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n# ... fill chat input and press Enter, wait 20-30s for response\n```\n\n## Step 5: Record results and take screenshots\n\n**Take a screenshot at EVERY significant test step** — before and after interactions, on success, and on failure. This is NON-NEGOTIABLE.\n\n**Required screenshot pattern for each test scenario:**\n```bash\n# BEFORE the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-before.png\n\n# Perform the action...\n\n# AFTER the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-after.png\n```\n\n**Naming convention:**\n```bash\n# Examples:\n# $RESULTS_DIR/01-login-page-before.png\n# $RESULTS_DIR/02-login-page-after.png\n# $RESULTS_DIR/03-credits-page-before.png\n# $RESULTS_DIR/04-credits-purchase-after.png\n# $RESULTS_DIR/05-negative-insufficient-credits.png\n# $RESULTS_DIR/06-error-state.png\n```\n\n**Minimum requirements:**\n- At least TWO screenshots per test scenario (before + after)\n- At least ONE screenshot for each negative test case showing the error state\n- If a test fails, screenshot the failure state AND any error logs visible in the UI\n\n## Step 6: Show results to user with screenshots\n\n**CRITICAL: After all tests complete, you MUST show every screenshot to the user using the Read tool, with an explanation of what each screenshot shows.** This is the most important part of the test report — the user needs to visually verify the results.\n\nFor each screenshot:\n1. Use the `Read` tool to display the PNG file (Claude can read images)\n2. Write a 1-2 sentence explanation below it describing:\n   - What page/state is being shown\n   - What the screenshot proves (which test scenario it validates)\n   - Any notable details visible in the UI\n\nFormat the output like this:\n\n```markdown\n### Screenshot 1: {descriptive title}\n[Read the PNG file here]\n\n**What it shows:** {1-2 sentence explanation of what this screenshot proves}\n\n---\n```\n\nAfter showing all screenshots, output a **detailed** summary table:\n\n| # | Scenario | Result | API Evidence | Screenshot Evidence |\n|---|----------|--------|-------------|-------------------|\n| 1 | {name} | PASS/FAIL | Before: X, After: Y | 01-before.png, 02-after.png |\n| 2 | ... | ... | ... | ... |\n\n**IMPORTANT:** As you show each screenshot and record test results, persist them in shell variables for Step 7:\n\n```bash\n# Build these variables during Step 6 — they are required by Step 7's script\n# NOTE: declare -A requires Bash 4.0+. This is standard on modern systems (macOS ships zsh\n# but Homebrew bash is 5.x; Linux typically has bash 5.x). If running on Bash <4, use a\n# plain variable with a lookup function instead.\ndeclare -A SCREENSHOT_EXPLANATIONS=(\n  [\"01-login-page.png\"]=\"Shows the login page loaded successfully with SSO options visible.\"\n  [\"02-builder-with-block.png\"]=\"The builder canvas displays the newly added block connected to the trigger.\"\n  # ... one entry per screenshot, using the same explanations you showed the user above\n)\n\nTEST_RESULTS_TABLE=\"| 1 | Login flow | PASS | N/A | 01-login-before.png, 02-login-after.png |\n| 2 | Credits purchase | PASS | Before: 100, After: 95 | 03-credits-before.png, 04-credits-after.png |\n| 3 | Insufficient credits (negative) | PASS | Credits: 0, rejected | 05-insufficient-credits-error.png |\"\n# ... one row per test scenario with actual results\n```\n\n## Step 7: Post test report as PR comment with screenshots\n\nPost the report with `gh pr comment --attach`. Each attached file is uploaded to GitHub's own asset store, and any image the body already references by that path is rewritten to point at the uploaded asset. Screenshots never touch a repo branch.\n\n**This step is MANDATORY. Every test run MUST post a PR comment — with the screenshots attached, or an image-free INCOMPLETE report when attachment is unavailable (see the fallback below). Never nothing.**\n\n**Attachments are permanent.** A `user-attachments` asset stays reachable by anyone holding its URL regardless of repo visibility, and deleting the comment does not reliably revoke it — there is no undo. Look at every screenshot for credentials, tokens, or customer data before this step runs.\n\n**Requires `gh` >= 2.99.0** — `--attach` landed there. Distro packages lag (Fedora's rpm tops out at 2.97.0), so check the version you are actually running before building the body:\n\n```bash\n# Too-old gh is not fatal here: the report still gets posted, just without\n# images, through the same fallback path a failed upload takes (below).\nGH_VERSION=$(gh version 2>/dev/null | head -1 | awk '{print $3}')\n# Numeric compare, not `sort -V`: BSD sort on macOS may lack -V, and an empty\n# substitution would silently read as \"too old\" on a perfectly good gh.\nGH_MAJOR=${GH_VERSION%%.*}; GH_REST=${GH_VERSION#*.}; GH_MINOR=${GH_REST%%.*}\nATTACH_OK=0\nATTACH_SKIP_REASON=\"\"\nif [ \"${GH_MAJOR:-0}\" -gt 2 ] 2>/dev/null || { [ \"${GH_MAJOR:-0}\" -eq 2 ] && [ \"${GH_MINOR:-0}\" -ge 99 ]; } 2>/dev/null; then\n  ATTACH_OK=1\nelse\n  ATTACH_SKIP_REASON=\"gh ${GH_VERSION:-not found} is below 2.99.0 — upgrade gh\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\nfi\n```\n\n**CRITICAL — NEVER post a bare directory link like `https://github.com/.../tree/...`.** Every screenshot MUST appear as `![name](url)` inline in the PR comment so reviewers can see them without clicking any links. After posting, the verification step below counts the rewritten asset URLs against the number of screenshots and exits 1 on any shortfall — the test run is incomplete until this passes.\n\n**CRITICAL — NEVER paste absolute local paths or credentials into the PR comment.** Strings like `/Users/…`, `/home/…`, `C:\\…` are useless to every reviewer except you, and a bearer token or JWT copied from the live stack's evidence is a real leak in a public comment. The build block below runs an egress check on the exact bytes about to be posted and aborts on either. The `./01-shot.png` references the attachments use are relative, not local paths, and are rewritten on upload — they are fine. Keep local paths in `$RESULTS_DIR/test-report.md` for yourself; only copy the *content* they reference (excerpts, test names, log lines) into the PR comment, not the path.\n\nBuild the body with **relative image references**, then attach the same paths:\n\n```bash\nREPO=\"Significant-Gravitas/AutoGPT\"\nMAX_ATTACHMENTS=50   # per gh pr comment invocation\n\n# Fail closed: an empty RESULTS_DIR would glob the PR worktree's PNGs into a public comment.\n# gh matches --attach to the body by exact path, so the cd is required; undone at the end.\n[ -n \"$RESULTS_DIR\" ] && [ -d \"$RESULTS_DIR\" ] || { echo \"ERROR: RESULTS_DIR is unset or not a directory: '$RESULTS_DIR'\"; exit 1; }\nSTEP7_ORIG_DIR=$PWD\ncd \"$RESULTS_DIR\" || exit 1\nNULLGLOB_WAS=$(shopt -p nullglob)   # restore the caller's setting, don't force it off\nshopt -s nullglob\nSCREENSHOT_FILES=(*.png)\n$NULLGLOB_WAS\nif [ ${#SCREENSHOT_FILES[@]} -eq 0 ]; then\n  echo \"ERROR: No screenshots found in $RESULTS_DIR. Test run is incomplete.\"\n  exit 1\nfi\n# Over the cap, post the report without images rather than nothing at all.\nif [ ${#SCREENSHOT_FILES[@]} -gt \"$MAX_ATTACHMENTS\" ]; then\n  ATTACH_SKIP_REASON=\"${#SCREENSHOT_FILES[@]} screenshots exceed the ${MAX_ATTACHMENTS}-attachment limit per comment\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\n  ATTACH_OK=0\nfi\n\nIMAGE_MARKDOWN=\"\"\nATTACH_ARGS=()\nfor BASENAME in \"${SCREENSHOT_FILES[@]}\"; do\n  TITLE=$(echo \"${BASENAME%.png}\" | sed 's/^[0-9]*-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')\n  EXPLANATION=\"${SCREENSHOT_EXPLANATIONS[$BASENAME]}\"\n  if [ -z \"$EXPLANATION\" ]; then\n    echo \"ERROR: Missing screenshot explanation for $BASENAME. Add it to SCREENSHOT_EXPLANATIONS in Step ","AGENTS.md":"# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/AGENTS.md)","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/CLAUDE.md)",".github/copilot-instructions.md":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.github/copilot-instructions.md)","autogpt_platform/AGENTS.md":"# autogpt_platform - AI Agent Protocol & Instructions\n\nPath: `autogpt_platform/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/AGENTS.md)","autogpt_platform/CLAUDE.md":"# autogpt_platform - Claude Agent Guidelines & System Prompt\n\nPath: `autogpt_platform/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/CLAUDE.md)","classic/CLAUDE.md":"# classic - Claude Agent Guidelines & System Prompt\n\nPath: `classic/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/CLAUDE.md)","docs/AGENTS.md":"# docs - AI Agent Protocol & Instructions\n\nPath: `docs/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/AGENTS.md)","docs/CLAUDE.md":"# docs - Claude Agent Guidelines & System Prompt\n\nPath: `docs/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/CLAUDE.md)",".claude/skills/vercel-react-best-practices/AGENTS.md":"# Vercel-react-best-practices Skill\n\nPath: `.claude/skills/vercel-react-best-practices/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/AGENTS.md)","autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json)","autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json)","autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json)","autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json)","autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json)","autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json)","autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json)","autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json)","autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json)","autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json)","autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json)","autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json)","autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json)","autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json)","autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json)","autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json)","autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json)","autogpt_platform/backend/agents/calculator-agent.json":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/calculator-agent.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/calculator-agent.json)",".claude/skills/orchestrate/scripts/capacity.sh":"# Claude Directive: capacity.sh\n\nPath: `.claude/skills/orchestrate/scripts/capacity.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/capacity.sh)",".claude/skills/orchestrate/scripts/classify-pane.sh":"# Claude Directive: classify-pane.sh\n\nPath: `.claude/skills/orchestrate/scripts/classify-pane.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/classify-pane.sh)",".claude/skills/orchestrate/scripts/find-spare.sh":"# Claude Directive: find-spare.sh\n\nPath: `.claude/skills/orchestrate/scripts/find-spare.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/find-spare.sh)",".claude/skills/orchestrate/scripts/notify.sh":"# Claude Directive: notify.sh\n\nPath: `.claude/skills/orchestrate/scripts/notify.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/notify.sh)",".claude/skills/orchestrate/scripts/poll-cycle.sh":"# Claude Directive: poll-cycle.sh\n\nPath: `.claude/skills/orchestrate/scripts/poll-cycle.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/poll-cycle.sh)",".claude/skills/orchestrate/scripts/recycle-agent.sh":"# Claude Directive: recycle-agent.sh\n\nPath: `.claude/skills/orchestrate/scripts/recycle-agent.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/recycle-agent.sh)",".claude/skills/orchestrate/scripts/run-loop.sh":"# Claude Directive: run-loop.sh\n\nPath: `.claude/skills/orchestrate/scripts/run-loop.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/run-loop.sh)",".claude/skills/orchestrate/scripts/spawn-agent.sh":"# Claude Directive: spawn-agent.sh\n\nPath: `.claude/skills/orchestrate/scripts/spawn-agent.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/spawn-agent.sh)",".claude/skills/orchestrate/scripts/status.sh":"# Claude Directive: status.sh\n\nPath: `.claude/skills/orchestrate/scripts/status.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/status.sh)",".claude/skills/orchestrate/scripts/verify-complete.sh":"# Claude Directive: verify-complete.sh\n\nPath: `.claude/skills/orchestrate/scripts/verify-complete.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/verify-complete.sh)",".claude/skills/perf-compare/scripts/perf_compare.py":"# Claude Directive: perf_compare.py\n\nPath: `.claude/skills/perf-compare/scripts/perf_compare.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/perf_compare.py)",".claude/skills/perf-compare/scripts/test_perf_compare.py":"# Claude Directive: test_perf_compare.py\n\nPath: `.claude/skills/perf-compare/scripts/test_perf_compare.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/test_perf_compare.py)",".claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md)",".claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md)",".claude/skills/vercel-react-best-practices/rules/async-api-routes.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-api-routes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md)",".claude/skills/vercel-react-best-practices/rules/async-defer-await.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-defer-await.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md)",".claude/skills/vercel-react-best-practices/rules/async-dependencies.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md)",".claude/skills/vercel-react-best-practices/rules/async-parallel.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-parallel.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-parallel.md)",".claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md)",".claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md)",".claude/skills/vercel-react-best-practices/rules/bundle-conditional.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md)",".claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md)",".claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md)",".claude/skills/vercel-react-best-practices/rules/bundle-preload.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-preload.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md)",".claude/skills/vercel-react-best-practices/rules/client-event-listeners.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md)",".claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md)",".claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md)",".claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md)",".claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md)",".claude/skills/vercel-react-best-practices/rules/js-cache-storage.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md)",".claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md)",".claude/skills/vercel-react-best-practices/rules/js-early-exit.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-early-exit.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md)",".claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md)",".claude/skills/vercel-react-best-practices/rules/js-index-maps.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-index-maps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md)",".claude/skills/vercel-react-best-practices/rules/js-length-check-first.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md)",".claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md)",".claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md)",".claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md)",".claude/skills/vercel-react-best-practices/rules/rendering-activity.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-activity.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md)",".claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md)",".claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md)",".claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md)",".claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md)",".claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md)",".claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md)",".claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md)",".claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md)",".claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md)",".claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md)",".claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md)",".claude/skills/vercel-react-best-practices/rules/rerender-memo.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-memo.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md)",".claude/skills/vercel-react-best-practices/rules/rerender-transitions.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md)",".claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md)",".claude/skills/vercel-react-best-practices/rules/server-cache-lru.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md)",".claude/skills/vercel-react-best-practices/rules/server-cache-react.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-cache-react.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md)",".claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md)",".claude/skills/vercel-react-best-practices/rules/server-serialization.md":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-serialization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-serialization.md)","classic/original_autogpt/autogpt/agents/README.md":"# Subagent: readme\n\nPath: `classic/original_autogpt/autogpt/agents/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/original_autogpt/autogpt/agents/README.md)",".claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json)",".claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json)",".claude/skills/perf-compare/scripts/fixtures/error_400.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/error_400.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/error_400.json)",".claude/skills/perf-compare/scripts/fixtures/project_builder.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/project_builder.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/project_builder.json)",".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json)",".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json)",".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json)",".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json)","autogpt_platform/backend/backend/copilot/tools/IDEAS.md":"# Tools Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/IDEAS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/IDEAS.md)","autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json":"# Tools Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json)","autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json":"# Testdata Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json)","autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md":"# Webapp-testing Skill\n\nPath: `autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/AGENTS.md)","tokens":300,"sizeBytes":162},{"name":"AGENTS.md","path":"autogpt_platform/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/AGENTS.md","title":"autogpt_platform - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# autogpt_platform - AI Agent Protocol & Instructions\n\nPath: `autogpt_platform/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/AGENTS.md)","tokens":300,"sizeBytes":215},{"name":"AGENTS.md","path":"docs/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/AGENTS.md","title":"docs - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# docs - AI Agent Protocol & Instructions\n\nPath: `docs/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/AGENTS.md)","tokens":300,"sizeBytes":179},{"name":"SKILL.md","path":".claude/skills/open-pr/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/open-pr/SKILL.md","title":"open-pr","category":"anthropic-skill","format":"markdown","content":"---\nname: open-pr\ndescription: Open a pull request with proper PR template, test coverage, and review workflow. Guides agents through creating a PR that follows repo conventions, ensures existing behaviors aren't broken, covers new behaviors with tests, and handles review via bot when local testing isn't possible. TRIGGER when user asks to \"open a PR\", \"create a PR\", \"make a PR\", \"submit a PR\", \"open pull request\", \"push and create PR\", or any variation of opening/submitting a pull request.\nuser-invocable: true\nargs: \"[base-branch] — optional target branch (defaults to dev).\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Open a Pull Request\n\n## Step 1: Pre-flight checks\n\nBefore opening the PR:\n\n1. Ensure all changes are committed\n2. Ensure the branch is pushed to the remote (`git push -u origin <branch>`)\n3. Run linters/formatters across the whole repo (not just changed files) and commit any fixes\n\n## Step 2: Test coverage\n\n**This is critical.** Before opening the PR, verify:\n\n### Existing behavior is not broken\n- Identify which modules/components your changes touch\n- Run the existing test suites for those areas\n- If tests fail, fix them before opening the PR — do not open a PR with known regressions\n\n### New behavior has test coverage\n- Every new feature, endpoint, or behavior change needs tests\n- If you added a new block, add tests for that block\n- If you changed API behavior, add or update API tests\n- If you changed frontend behavior, verify it doesn't break existing flows\n\nIf you cannot run the full test suite locally, note which tests you ran and which you couldn't in the test plan.\n\n## Step 3: Create the PR using the repo template\n\nRead the canonical PR template at `.github/PULL_REQUEST_TEMPLATE.md` and use it **verbatim** as your PR body:\n\n1. Read the template: `cat .github/PULL_REQUEST_TEMPLATE.md`\n2. Preserve the exact section titles and formatting, including:\n   - `### Why / What / How`\n   - `### Changes 🏗️`\n   - `### Checklist 📋`\n3. Replace HTML comment prompts (`<!-- ... -->`) with actual content; do not leave them in\n4. **Do not pre-check boxes** — leave all checkboxes as `- [ ]` until each step is actually completed\n5. Do not alter the template structure, rename sections, or remove any checklist items\n\n**PR title must use conventional commit format** (e.g., `feat(backend): add new block`, `fix(frontend): resolve routing bug`, `dx(skills): update PR workflow`). See CLAUDE.md for the full list of scopes.\n\nUse `gh pr create` with the base branch (defaults to `dev` if no `[base-branch]` was provided). Use `--body-file` to avoid shell interpretation of backticks and special characters:\n\n```bash\nBASE_BRANCH=\"${BASE_BRANCH:-dev}\"\nPR_BODY=$(mktemp)\ncat > \"$PR_BODY\" << 'PREOF'\n<filled-in template from .github/PULL_REQUEST_TEMPLATE.md>\nPREOF\ngh pr create --base \"$BASE_BRANCH\" --title \"<type>(scope): short description\" --body-file \"$PR_BODY\"\nrm \"$PR_BODY\"\n```\n\n## Step 4: Review workflow\n\n### If you have a workspace that allows testing (docker, running backend, etc.)\n- Run `/pr-test` to do E2E manual testing of the PR using docker compose, agent-browser, and API calls. This is the most thorough way to validate your changes before review.\n- After testing, run `/pr-review` to self-review the PR for correctness, security, code quality, and testing gaps before requesting human review.\n\n### If you do NOT have a workspace that allows testing\nThis is common for agents running in worktrees without a full stack. In this case:\n\n1. Run `/pr-review` locally to catch obvious issues before pushing\n2. **Comment `/review` on the PR** after creating it to trigger the review bot\n3. **Poll for the review** rather than blindly waiting — check for new review comments every 30 seconds using `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate` and the GraphQL inline threads query. The bot typically responds within 30 minutes, but polling lets the agent react as soon as it arrives.\n4. Do NOT proceed or merge until the bot review comes back — but bound the\n   wait at **60 minutes** from the `/review` comment, the same budget\n   `/pr-address` and `/pr-polish` use. If nothing has arrived by then, stop\n   waiting and tell the user the requested review never came. Never block\n   indefinitely on a bot.\n5. Address any issues the bot raises — use `/pr-address` which has a full polling loop with CI + comment tracking\n\n```bash\n# After creating the PR:\nPR_NUMBER=$(gh pr view --json number -q .number)\ngh pr comment \"$PR_NUMBER\" --body \"/review\"\n# Then use /pr-address to poll for and address the review when it arrives\n```\n\n## Step 5: Address review feedback\n\nOnce the review bot or human reviewers leave comments:\n- Run `/pr-address` to address review comments. It will loop until CI is green and all comments are resolved.\n- Do not merge without human approval.\n\n## Related skills\n\n| Skill | When to use |\n|---|---|\n| `/pr-test` | E2E testing with docker compose, agent-browser, API calls — use when you have a running workspace |\n| `/pr-review` | Review for correctness, security, code quality — use before requesting human review |\n| `/pr-address` | Address reviewer comments and loop until CI green — use after reviews come in; it can also post the `/review` trigger itself if the PR has no review yet |\n\n## Step 6: Post-creation\n\nAfter the PR is created and review is triggered:\n- Share the PR URL with the user\n- If waiting on the review bot, let the user know the expected wait time (~30 min)\n- Do not merge without human approval\n","frontmatter":{"name":"open-pr","description":"Open a pull request with proper PR template, test coverage, and review workflow. Guides agents through creating a PR that follows repo conventions, ensures existing behaviors aren't broken, covers new behaviors with tests, and handles review via bot when local testing isn't possible. TRIGGER when user asks to \"open a PR\", \"create a PR\", \"make a PR\", \"submit a PR\", \"open pull request\", \"push and create PR\", or any variation of opening/submitting a pull request.","user-invocable":true,"args":"[base-branch] — optional target branch (defaults to dev).","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":1317,"sizeBytes":5548},{"name":"SKILL.md","path":".claude/skills/orchestrate/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/SKILL.md","title":"orchestrate","category":"anthropic-skill","format":"markdown","content":"---\nname: orchestrate\ndescription: \"Meta-agent supervisor that manages a fleet of Claude Code agents running in tmux windows. Auto-discovers spare worktrees, spawns agents, monitors state, kicks idle agents, approves safe confirmations, and recycles worktrees when done. TRIGGER when user asks to supervise agents, run parallel tasks, manage worktrees, check agent status, or orchestrate parallel work.\"\nuser-invocable: true\nargument-hint: \"any free text — e.g. 'start 3 agents on X Y Z', 'show status', 'add task: implement feature A', 'stop', 'how many are free?'\"\nmetadata:\n  author: autogpt-team\n  version: \"6.0.0\"\n---\n\n# Orchestrate — Agent Fleet Supervisor\n\nOne tmux session, N windows — each window is one agent working in its own worktree. Speak naturally; Claude maps your intent to the right scripts.\n\n## Scripts\n\n```bash\nSKILLS_DIR=$(git rev-parse --show-toplevel)/.claude/skills/orchestrate/scripts\nSTATE_FILE=~/.claude/orchestrator-state.json\n```\n\n| Script | Purpose |\n|---|---|\n| `find-spare.sh [REPO_ROOT]` | List free worktrees — one `PATH BRANCH` per line |\n| `spawn-agent.sh SESSION PATH SPARE NEW_BRANCH OBJECTIVE [PR_NUMBER] [STEPS...]` | Create window + checkout branch + launch claude + send task. **Stdout: `SESSION:WIN` only** |\n| `recycle-agent.sh WINDOW PATH SPARE_BRANCH` | Kill window + restore spare branch |\n| `run-loop.sh` | **Mechanical babysitter** — idle restart + dialog approval + recycle on ORCHESTRATOR:DONE + supervisor health check + all-done notification |\n| `verify-complete.sh WINDOW` | Verify PR is done: checkpoints ✓ + 0 unresolved threads + CI green + no fresh CHANGES_REQUESTED. Repo auto-derived from state file `.repo` or git remote. |\n| `notify.sh MESSAGE` | Send notification via Discord webhook (env `DISCORD_WEBHOOK_URL` or state `.discord_webhook`), macOS notification center, and stdout |\n| `capacity.sh [REPO_ROOT]` | Print available + in-use worktrees |\n| `status.sh` | Print fleet status + live pane commands |\n| `poll-cycle.sh` | One monitoring cycle — classifies panes, tracks checkpoints, returns JSON action array |\n| `classify-pane.sh WINDOW` | Classify one pane state |\n\n## Supervision model\n\n```\nOrchestrating Claude (this Claude session — IS the supervisor)\n  └── Reads pane output, checks CI, intervenes with targeted guidance\n        run-loop.sh (separate tmux window, every 30s)\n          └── Mechanical only: idle restart, dialog approval, recycle on ORCHESTRATOR:DONE\n```\n\n**You (the orchestrating Claude)** are the supervisor. After spawning agents, stay in this conversation and actively monitor: poll each agent's pane every 2-3 minutes, check CI, nudge stalled agents, and verify completions. Do not spawn a separate supervisor Claude window — it loses context, is hard to observe, and compounds context compression problems.\n\n**run-loop.sh** is the mechanical layer — zero tokens, handles things that need no judgment: restart crashed agents, press Enter on dialogs, recycle completed worktrees (only after `verify-complete.sh` passes).\n\n## Checkpoint protocol\n\nAgents output checkpoints as they complete each required step:\n\n```\nCHECKPOINT:<step-name>\n```\n\nRequired steps are passed as args to `spawn-agent.sh` (e.g. `pr-address pr-test`). `run-loop.sh` will not recycle a window until all required checkpoints are found in the pane output. If `verify-complete.sh` fails, the agent is re-briefed automatically.\n\n## Worktree lifecycle\n\n```text\nspare/N branch  →  spawn-agent.sh (--session-id UUID)  →  window + feat/branch + claude running\n                                                                 ↓\n                                               CHECKPOINT:<step> (as steps complete)\n                                                                 ↓\n                                                        ORCHESTRATOR:DONE\n                                                                 ↓\n                                    verify-complete.sh: checkpoints ✓ + 0 threads + CI green + no fresh CHANGES_REQUESTED\n                                                                 ↓\n                                              state → \"done\", notify, window KEPT OPEN\n                                                                 ↓\n                              user/orchestrator explicitly requests recycle\n                                                                 ↓\n                                         recycle-agent.sh → spare/N (free again)\n```\n\n**Windows are never auto-killed.** The worktree stays on its branch, the session stays alive. The agent is done working but the window, git state, and Claude session are all preserved until you choose to recycle.\n\n**To resume a done or crashed session:**\n```bash\n# Resume by stored session ID (preferred — exact session, full context)\nclaude --resume SESSION_ID --permission-mode bypassPermissions\n\n# Or resume most recent session in that worktree directory\ncd /path/to/worktree && claude --continue --permission-mode bypassPermissions\n```\n\n**To manually recycle when ready:**\n```bash\nbash ~/.claude/orchestrator/scripts/recycle-agent.sh SESSION:WIN WORKTREE_PATH spare/N\n# Then update state:\njq --arg w \"SESSION:WIN\" '.agents |= map(if .window == $w then .state = \"recycled\" else . end)' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## State file (`~/.claude/orchestrator-state.json`)\n\nNever committed to git. You maintain this file directly using `jq` + atomic writes (`.tmp` → `mv`).\n\n```json\n{\n  \"active\": true,\n  \"tmux_session\": \"autogpt1\",\n  \"idle_threshold_seconds\": 300,\n  \"loop_window\": \"autogpt1:5\",\n  \"repo\": \"Significant-Gravitas/AutoGPT\",\n  \"discord_webhook\": \"https://discord.com/api/webhooks/...\",\n  \"last_poll_at\": 0,\n  \"agents\": [\n    {\n      \"window\": \"autogpt1:3\",\n      \"worktree\": \"AutoGPT6\",\n      \"worktree_path\": \"/path/to/AutoGPT6\",\n      \"spare_branch\": \"spare/6\",\n      \"branch\": \"feat/my-feature\",\n      \"objective\": \"Implement X and open a PR\",\n      \"pr_number\": \"12345\",\n      \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"steps\": [\"pr-address\", \"pr-test\"],\n      \"checkpoints\": [\"pr-address\"],\n      \"state\": \"running\",\n      \"last_output_hash\": \"\",\n      \"last_seen_at\": 0,\n      \"spawned_at\": 0,\n      \"idle_since\": 0,\n      \"revision_count\": 0,\n      \"last_rebriefed_at\": 0\n    }\n  ]\n}\n```\n\nTop-level optional fields:\n- `repo` — GitHub `owner/repo` for CI/thread checks. Auto-derived from git remote if omitted.\n- `discord_webhook` — Discord webhook URL for completion notifications. Also reads `DISCORD_WEBHOOK_URL` env var.\n\nPer-agent fields:\n- `session_id` — UUID passed to `claude --session-id` at spawn; use with `claude --resume UUID` to restore exact session context after a crash or window close.\n- `last_rebriefed_at` — Unix timestamp of last re-brief; enforces 5-min cooldown to prevent spam.\n\nAgent states: `running` | `idle` | `stuck` | `waiting_approval` | `complete` | `done` | `escalated`\n\n`done` means verified complete — window is still open, session still alive, worktree still on task branch. Not recycled yet.\n\n## Serial /pr-test rule\n\n`/pr-test` and `/pr-test --fix` run local Docker + integration tests that use shared ports, a shared database, and shared build caches. **Running two `/pr-test` jobs simultaneously will cause port conflicts and database corruption.**\n\n**Rule: only one `/pr-test` runs at a time. The orchestrator serializes them.**\n\nYou (the orchestrating Claude) own the test queue:\n1. Agents do `pr-review` and `pr-address` in parallel — that's safe (they only push code and reply to GitHub).\n2. When a PR needs local testing, add it to your mental queue — don't give agents a `pr-test` step.\n3. Run `/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER --fix` yourself, sequentially.\n4. Feed results back to the relevant agent via `tmux send-keys`:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"Local tests for PR #N: <paste failure output or 'all passed'>. Fix any failures and push, then output ORCHESTRATOR:DONE.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n5. Wait for CI to confirm green before marking the agent done.\n\nIf multiple PRs need testing at the same time, pick the one furthest along (fewest pending CI checks) and test it first. Only start the next test after the previous one completes.\n\n## Session restore (tested and confirmed)\n\nAgent sessions are saved to disk. To restore a closed or crashed session:\n\n```bash\n# If session_id is in state (preferred):\nNEW_WIN=$(tmux new-window -t SESSION -n WORKTREE_NAME -P -F '#{window_index}')\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --resume SESSION_ID --permission-mode bypassPermissions\" Enter\n\n# If no session_id (use --continue for most recent session in that directory):\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --continue --permission-mode bypassPermissions\" Enter\n```\n\n`--continue` restores the full conversation history including all tool calls, file edits, and context. The agent resumes exactly where it left off. After restoring, update the window address in the state file:\n\n```bash\njq --arg old \"SESSION:OLD_WIN\" --arg new \"SESSION:NEW_WIN\" \\\n  '(.agents[] | select(.window == $old)).window = $new' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## Intent → action mapping\n\nMatch the user's message to one of these intents:\n\n| The user says something like… | What to do |\n|---|---|\n| \"status\", \"what's running\", \"show agents\" | Run `status.sh` + `capacity.sh`, show output |\n| \"how many free\", \"capacity\", \"available worktrees\" | Run `capacity.sh`, show output |\n| \"start N agents on X, Y, Z\" or \"run these tasks: …\" | See **Spawning agents** below |\n| \"add task: …\", \"add one more agent for …\" | See **Adding an agent** below |\n| \"stop\", \"shut down\", \"pause the fleet\" | See **Stopping** below |\n| \"poll\", \"check now\", \"run a cycle\" | Run `poll-cycle.sh`, process actions |\n| \"recycle window X\", \"free up autogpt3\" | Run `recycle-agent.sh` directly |\n\nWhen the intent is ambiguous, show capacity first and ask what tasks to run.\n\n## Spawning agents\n\n### 1. Resolve tmux session\n\n```bash\ntmux list-sessions -F \"#{session_name}: #{session_windows} windows\" 2>/dev/null\n```\n\nUse an existing session. **Never create a tmux session from within Claude** — it becomes a child of Claude's process and dies when the session ends. If no session exists, tell the user to run `tmux new-session -d -s autogpt1` in their terminal first, then re-invoke `/orchestrate`.\n\n### 2. Show available capacity\n\n```bash\nbash $SKILLS_DIR/capacity.sh $(git rev-parse --show-toplevel)\n```\n\n### 3. Collect tasks from the user\n\nFor each task, gather:\n- **objective** — what to do (e.g. \"implement feature X and open a PR\")\n- **branch name** — e.g. `feat/my-feature` (derive from objective if not given)\n- **pr_number** — GitHub PR number if working on an existing PR (for verification)\n- **steps** — required checkpoint names in order (e.g. `pr-address pr-test`) — derive from objective\n\nAsk for `idle_threshold_seconds` only if the user mentions it (default: 300).\n\nNever ask the user to specify a worktree — auto-assign from `find-spare.sh`.\n\n### 4. Spawn one agent per task\n\n```bash\n# Get ordered list of spare worktrees\nSPARE_LIST=$(bash $SKILLS_DIR/find-spare.sh $(git rev-parse --show-toplevel))\n\n# For each task, take the next spare line:\nWORKTREE_PATH=$(echo \"$SPARE_LINE\" | awk '{print $1}')\nSPARE_BRANCH=$(echo \"$SPARE_LINE\" | awk '{print $2}')\n\n# With PR number and required steps:\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\" \"$PR_NUMBER\" \"pr-address\" \"pr-test\")\n\n# Without PR (new work):\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\")\n```\n\nBuild an agent record and append it to the state file. If the state file doesn't exist yet, initialize it:\n\n```bash\n# Derive repo from git remote (used by verify-complete.sh + supervisor)\nREPO=$(git remote get-url origin 2>/dev/null | sed 's|.*github\\.com[:/]||; s|\\.git$||' || echo \"\")\n\njq -n \\\n  --arg session \"$SESSION\" \\\n  --arg repo \"$REPO\" \\\n  --argjson threshold 300 \\\n  '{active:true, tmux_session:$session, idle_threshold_seconds:$threshold,\n    repo:$repo, loop_window:null, supervisor_window:null, last_poll_at:0, agents:[]}' \\\n  > ~/.claude/orchestrator-state.json\n```\n\nOptionally add a Discord webhook for completion notifications:\n```bash\njq --arg hook \"$DISCORD_WEBHOOK_URL\" '.discord_webhook = $hook' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n`spawn-agent.sh` writes the initial agent record (window, worktree_path, branch, objective, state, etc.) to the state file automatically — **do not append the record again after calling it.** The record already exists and `pr_number`/`steps` are patched in by the script itself.\n\n### 5. Start the mechanical babysitter\n\n```bash\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\n\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n### 6. Begin supervising directly in this conversation\n\nYou are the supervisor. After spawning, immediately start your first poll loop (see **Supervisor duties** below) and continue every 2-3 minutes. Do NOT spawn a separate supervisor Claude window.\n\n## Adding an agent\n\nFind the next spare worktree, then spawn and append to state — same as steps 2–4 above but for a single task. If no spare worktrees are available, tell the user.\n\n## Supervisor duties (YOUR job, every 2-3 min in this conversation)\n\nYou are the supervisor. Run this poll loop directly in your Claude session — not in a separate window.\n\n### Poll loop mechanism\n\nYou are reactive — you only act when a tool completes or the user sends a message. To create a self-sustaining poll loop without user involvement:\n\n1. Start each poll with `run_in_background: true` + a sleep before the work:\n   ```bash\n   sleep 120 && tmux capture-pane -t autogpt1:0 -p -S -200 | tail -40\n   # + similar for each active window\n   ```\n2. When the background job notifies you, read the pane output and take action.\n3. Immediately schedule the next background poll — this keeps the loop alive.\n4. Stop scheduling when all agents are done/escalated.\n\n**Never tell the user \"I'll poll every 2-3 minutes\"** — that does nothing without a trigger. Start the background job instead.\n\n### Each poll: what to check\n\n```bash\n# 1. Read state\ncat ~/.claude/orchestrator-state.json | jq '.agents[] | {window, worktree, branch, state, pr_number, checkpoints}'\n\n# 2. For each running/stuck/idle agent, capture pane\ntmux capture-pane -t SESSION:WIN -p -S -200 | tail -60\n```\n\nFor each agent, decide:\n\n| What you see | Action |\n|---|---|\n| Spinner / tools running | Do nothing — agent is working |\n| Idle `❯` prompt, no `ORCHESTRATOR:DONE` | Stalled — send specific nudge with objective from state |\n| Stuck in error loop | Send targeted fix with exact error + solution |\n| Waiting for input / question | Answer and unblock via `tmux send-keys` |\n| CI red | `gh pr checks PR_NUMBER --repo REPO` → tell agent exactly what's failing |\n| GitHub abuse rate limit error | Nudge: \"Wait 60 seconds then continue posting replies with sleep 3 between each\" |\n| Context compacted / agent lost | Send recovery: `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"WIN\")'` + `gh pr view PR_NUMBER --json title,body` |\n| `ORCHESTRATOR:DONE` in output | Query GraphQL for actual unresolved count. If >0, re-brief. If 0, run `verify-complete.sh` |\n\n**Poll all windows from state, not from memory.** Before each poll, run:\n```bash\njq -r '.agents[] | select(.state | test(\"running|idle|stuck|waiting_approval|pending_evaluation\")) | .window' ~/.claude/orchestrator-state.json\n```\nand capture every window listed. If you manually added a window outside spawn-agent.sh, ensure it's in the state file first.\n\n### RUNNING count includes waiting_approval agents\n\nThe `RUNNING` count from run-loop.sh includes agents in `waiting_approval` state (they match the regex `running|stuck|waiting_approval|idle`). This means a fleet that is only `waiting_approval` still shows RUNNING > 0 in the log — it does **not** mean agents are actively working.\n\nWhen you see `RUNNING > 0` in the run-loop log but suspect agents are actually blocked, check state directly:\n```bash\njq '.agents[] | {window, state, worktree}' ~/.claude/orchestrator-state.json\n```\nA count of `running=1 waiting=1` in the log actually means one agent is waiting for approval — the orchestrator should check and approve, not wait.\n\n### State file staleness recovery\n\nThe state file is written by scripts but can drift from reality when windows are closed, sessions expire, or the orchestrator restarts across conversations.\n\n**Signs of stale state:**\n- `loop_window` points to a window that no longer exists in the tmux session\n- An agent's `state` is `running` but tmux window is closed or shows a shell prompt (not claude)\n- `last_seen_at` is hours old but state still says `running`\n\n**Recovery steps:**\n\n1. **Verify actual tmux windows:**\n```bash\ntmux list-windows -t SESSION -F '#{window_index}: #{window_name} (#{pane_current_command})'\n```\n\n2. **Cross-reference with state file:**\n```bash\njq -r '.agents[] | \"\\(.window) \\(.state) \\(.worktree)\"' ~/.claude/orchestrator-state.json\n```\n\n3. **Fix stale entries:**\n```bash\n# Agent window closed — mark idle so run-loop.sh will restart it\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window==$w)).state = \"idle\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\n# loop_window gone — kill the stale reference, then restart run-loop.sh\njq '.loop_window = null' ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n4. **After any state repair, re-run `status.sh` to confirm coherence before resuming supervision.**\n\n### Strict ORCHESTRATOR:DONE gate\n\n`verify-complete.sh` handles the main checks automatically (checkpoints, threads, CI green, spawned_at, and CHANGES_REQUESTED). Run it:\n\n**CHANGES_REQUESTED staleness rule**: a `CHANGES_REQUESTED` review only blocks if it was submitted *after* the latest commit. If the latest commit postdates the review, the review is considered stale (feedback already addressed) and does not block. This avoids false negatives when a bot reviewer hasn't re-reviewed after the agent's fixing commits.\n\n```bash\nSKILLS_DIR=~/.claude/orchestrator/scripts\nbash $SKILLS_DIR/verify-complete.sh SESSION:WIN\n```\n\nIf it passes → run-loop.sh will recycle the window automatically. No manual action needed.\nIf it fails → re-brief the agent with the failure reason. Never manually mark state `done` to bypass this.\n\n### Re-brief a stalled agent\n\n**Before sending any nudge, verify the pane is at an idle ❯ prompt.** Sending text into a still-processing pane produces stuck `[Pasted text +N lines]` that the agent never sees.\n\nCheck:\n```bash\ntmux capture-pane -t SESSION:WIN -p 2>/dev/null | tail -5\n```\nIf the last line shows a spinner (✳✽✢✶·), `Running…`, or no `❯` — wait 10–15s and check again before sending.\n\n```bash\nOBJ=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .objective' ~/.claude/orchestrator-state.json)\nPR=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .pr_number' ~/.claude/orchestrator-state.json)\ntmux send-keys -t SESSION:WIN \"You appear stalled. Your objective: $OBJ. Check: gh pr view $PR --json title,body,headRefName to reorient.\"\nsleep 0.3\ntmux send-keys -t SESSION:WIN Enter\n```\n\nIf `image_path` is set on the agent record, include: \"Re-read context at IMAGE_PATH with the Read tool.\"\n\n## Self-recovery protocol (agents)\n\nspawn-agent.sh automatically includes this instruction in every objective:\n\n> If your context compacts and you lose track of what to do, run:\n> `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"SESSION:WIN\")'`\n> and `gh pr view PR_NUMBER --json title,body,headRefName` to reorient.\n> Output each completed step as `CHECKPOINT:<step-name>` on its own line.\n\n## Passing images and screenshots to agents\n\n`tmux send-keys` is text-only — you cannot paste a raw image into a pane. To give an agent visual context (screenshots, diagrams, mockups):\n\n1. **Save the image to a temp file** with a stable path:\n   ```bash\n   # If the user drags in a screenshot or you receive a file path:\n   IMAGE_PATH=\"/tmp/orchestrator-context-$(date +%s).png\"\n   cp \"$USER_PROVIDED_PATH\" \"$IMAGE_PATH\"\n   ```\n\n2. **Reference the path in the objective string**:\n   ```bash\n   OBJECTIVE=\"Implement the layout shown in /tmp/orchestrator-context-1234567890.png. Read that image first with the Read tool to understand the design.\"\n   ```\n\n3. The agent uses its `Read` tool to view the image at startup — Claude Code agents are multimodal and can read image files directly.\n\n**Rule**: always use `/tmp/orchestrator-context-<timestamp>.png` as the naming convention so the supervisor knows what to look for if it needs to re-brief an agent with the same image.\n\n---\n\n## Orchestrator final evaluation (YOU decide, not the script)\n\n`verify-complete.sh` is a gate — it blocks premature marking. But it cannot tell you if the work is actually good. That is YOUR job.\n\nWhen run-loop marks an agent `pending_evaluation` and you're notified, do all of these before marking done:\n\n### 1. Run /pr-test (required, serialized, use TodoWrite to queue)\n\n`/pr-test` is the only reliable confirmation that the objective is actually met. Run it yourself, not the agent.\n\n**When multiple PRs reach `pending_evaluation` at the same time, use TodoWrite to queue them:**\n```\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/NNNN — <feature description>\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/MMMM — <feature description>\n```\nRun one at a time. Check off as you go.\n\n```\n/pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/PR_NUMBER\n```\n\n**/pr-test can be lazy** — if it gives vague output, re-run with full context:\n\n```\n/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER\nContext: This PR implements <objective from state file>. Key files: <list>.\nPlease verify: <specific behaviors to check>.\n```\n\nOnly one `/pr-test` at a time — they share ports and DB.\n\n### /pr-test result evaluation\n\n**PARTIAL on any headline feature scenario is an immediate blocker.** Do not approve, do not mark done, do not let the agent output `ORCHESTRATOR:DONE`.\n\n| `/pr-test` result | Action |\n|---|---|\n| All headline scenarios **PASS** | Proceed to evaluation step 2 |\n| Any headline scenario **PARTIAL** | Re-brief the agent immediately — see below |\n| Any headline scenario **FAIL** | Re-brief the agent immediately |\n\n**What PARTIAL means**: the feature is only partly working. Example: the Apply button never appeared, or the AI returned no action blocks. The agent addressed part of the objective but not all of it.\n\n**When any headline scenario is PARTIAL or FAIL:**\n\n1. Do NOT mark the agent done or accept `ORCHESTRATOR:DONE`\n2. Re-brief the agent with the specific scenario that failed and what was missing:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"PARTIAL result on /pr-test — S5 (Apply button) never appeared. The AI must output JSON action blocks for the Apply button to render. Fix this before re-running /pr-test.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n3. Set state back to `running`:\n   ```bash\n   jq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"running\"' \\\n     ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n   ```\n4. Wait for new `ORCHESTRATOR:DONE`, then re-run `/pr-test` from scratch\n\n**Rule: only ALL-PASS qualifies for approval.** A mix of PASS + PARTIAL is a failure.\n\n> **Why this matters**: A PR was once wrongly approved with S5 PARTIAL — the AI never output JSON action blocks so the Apply button never appeared. The fix was already in the agent's reach but slipped through because PARTIAL was not treated as blocking.\n\n### 2. Do your own evaluation\n\n1. **Read the PR diff and objective** — does the code actually implement what was asked? Is anything obviously missing or half-done?\n2. **Read the resolved threads** — were comments addressed with real fixes, or just dismissed/resolved without changes?\n3. **Check CI run names** — any suspicious retries that shouldn't have passed?\n4. **Check the PR description** — title, summary, test plan complete?\n\n### 3. Decide\n\n- `/pr-test` all scenarios PASS + evaluation looks good → mark `done` in state, tell the user the PR is ready, ask if window should be closed\n- `/pr-test` any scenario PARTIAL or FAIL → re-brief the agent with the specific failing scenario, set state back to `running` (see `/pr-test result evaluation` above)\n- Evaluation finds gaps even with all PASS → re-brief the agent with specific gaps, set state back to `running`\n\n**Never mark done based purely on script output.** You hold the full objective context; the script does not.\n\n```bash\n# Mark done after your positive evaluation:\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"done\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## When to stop the fleet\n\nStop the fleet (`active = false`) when **all** of the following are true:\n\n| Check | How to verify |\n|---|---|\n| All agents are `done` or `escalated` | `jq '[.agents[] | select(.state | test(\"running\\|stuck\\|idle\\|waiting_approval\"))] | length' ~/.claude/orchestrator-state.json` == 0 |\n| All PRs have 0 unresolved review threads | GraphQL `isResolved` check per PR |\n| All PRs have green CI **on a run triggered after the agent's last push** | `gh run list --branch BRANCH --limit 1` timestamp > `spawned_at` in state |\n| No fresh CHANGES_REQUESTED (after latest commit) | `verify-complete.sh` checks this — stale pre-commit reviews are ignored |\n| No agents are `escalated` without human review | If any are escalated, surface to user first |\n\n**Do NOT stop just because agents output `ORCHESTRATOR:DONE`.** That is a signal to verify, not a signal to stop.\n\n**Do stop** if the user explicitly says \"stop\", \"shut down\", or \"kill everything\", even with agents still running.\n\n```bash\n# Graceful stop\njq '.active = false' ~/.claude/orchestrator-state.json > /tmp/orch.tmp \\\n  && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\nLOOP_WINDOW=$(jq -r '.loop_window // \"\"' ~/.claude/orchestrator-state.json)\n[ -n \"$LOOP_WINDOW\" ] && tmux kill-window -t \"$LOOP_WINDOW\" 2>/dev/null || true\n```\n\nDoes **not** recycle running worktrees — agents may still be mid-task. Run `capacity.sh` to see what's still in progress.\n\n## tmux send-keys pattern\n\n**Always split long messages into text + Enter as two separate calls with a sleep between them.** If sent as one call (`\"text\" Enter`), Enter can fire before the full string is buffered into Claude's input — leaving the message stuck as `[Pasted text +N lines]` unsent.\n\n```bash\n# CORRECT — text then Enter separately\ntmux send-keys -t \"$WINDOW\" \"your long message here\"\nsleep 0.3\ntmux send-keys -t \"$WINDOW\" Enter\n\n# WRONG — Enter may fire before text is buffered\ntmux send-keys -t \"$WINDOW\" \"your long message here\" Enter\n```\n\nShort single-character sends (`y`, `Down`, empty Enter for dialog approval) are safe to combine since they have no buffering lag.\n\n---\n\n## Protected worktrees\n\nSome worktrees must **never** be used as spare worktrees for agent tasks because they host files critical to the orchestrator itself:\n\n| Worktree | Protected branch | Why |\n|---|---|---|\n| `AutoGPT1` | `dx/orchestrate-skill` | Hosts the orchestrate skill scripts. `recycle-agent.sh` would check out `spare/1`, wiping `.claude/skills/` and breaking all subsequent `spawn-agent.sh` calls. |\n\n**Rule**: when selecting spare worktrees via `find-spare.sh`, skip any worktree whose CURRENT branch matches a protected branch. If you accidentally spawn an agent in a protected worktree, do not let `recycle-agent.sh` run on it — manually restore the branch after the agent finishes.\n\nWhen `dx/orchestrate-skill` is merged into `dev`, `AutoGPT1` becomes a normal spare again.\n\n---\n\n## Thread resolution integrity (critical)\n\n**Agents MUST NOT resolve review threads via GraphQL unless a real code fix has been committed and pushed first.**\n\nThis is the most common failure mode: agents call `resolveReviewThread` to make unresolved counts drop without actually fixing anything. This produces a false \"done\" signal that gets past verify-complete.sh.\n\n**The only valid resolution sequence:**\n1. Read the thread and understand what it's asking\n2. Make the actual code change\n3. `git commit` and `git push`\n4. Reply to the thread with the commit SHA (e.g. \"Fixed in `abc1234`\")\n5. THEN call `resolveReviewThread`\n\n**The supervisor must verify actual thread counts via GraphQL** — never trust an agent's claim of \"0 unresolved.\" After any agent's ORCHESTRATOR:DONE, always run:\n\n```bash\n# Step 1: get total count\nTOTAL=$(gh api graphql -f query='{ repository(owner: \"OWNER\", name: \"REPO\") { pullRequest(number: PR) { reviewThreads { totalCount } } } }' \\\n  | jq '.data.repository.pullRequest.reviewThreads.totalCount')\necho \"Total threads: $TOTAL\"\n\n# Step 2: paginate all pages and count unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"{ repository(owner: \\\"OWNER\\\", name: \\\"REPO\\\") { pullRequest(number: PR) { reviewThreads(first: 100${AFTER}) { pageInfo { hasNextPage endCursor } nodes { isResolved } } } } }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved: $UNRESOLVED\"\n```\n\nIf unresolved > 0, the agent is NOT done — re-brief with the actual count and the rule.\n\n**Include this in every agent objective:**\n> IMPORTANT: Do NOT resolve any review thread via GraphQL unless the code fix is committed and pushed first. Fix the code → commit → push → reply with SHA → then resolve. Never resolve without a real commit. \"Accepted\" or \"Acknowledged\" replies are NOT resolutions — only real commits qualify.\n\n### Detecting fake resolutions\n\nWhen an agent claims \"0 unresolved threads\", query GitHub GraphQL yourself and also inspect how each thread was resolved. A resolved thread whose last comment is `\"Acknowledged\"`, `\"Same as above\"`, `\"Accepted trade-off\"`, or `\"Deferred\"` — with no commit SHA — is a fake resolution.\n\nTo spot these, paginate all pages and collect resolved threads with missing SHA links:\n```bash\n# Paginate all pages — first:100 misses threads beyond page 1 on large PRs\nCURSOR=\"\"; FAKE_RESOLUTIONS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: PR_NUMBER) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved\n            comments(last: 1) {\n              nodes { body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  PAGE_FAKES=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[]\n    | select(.isResolved == true)\n    | {body: .comments.nodes[0].body[:120], author: .comments.nodes[0].author.login}\n    | select(.body | test(\"Fixed in|Removed in|Addressed in\") | not)]')\n  FAKE_RESOLUTIONS=$(echo \"$FAKE_RESOLUTIONS $PAGE_FAKES\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"$FAKE_RESOLUTIONS\"\n```\nAny resolved thread whose last comment does NOT contain `\"Fixed in\"`, `\"Removed in\"`, or `\"Addressed in\"` (with a commit link) should be investigated — either the agent falsely resolved it, or it was a genuine false positive that needs explanation.\n\n## GitHub abuse rate limits\n\nTwo distinct rate limits exist with different recovery times:\n\n| Error | HTTP status | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` in body | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `API rate limit exceeded` | 429 | Primary rate limit — too many read calls per hour | Wait until `X-RateLimit-Reset` timestamp |\n\n**Prevention:** Agents must add `sleep 3` between individual thread reply API calls. For >20 unresolved threads, increase to `sleep 5`.\n\nIf you see a 403 `abuse` error from an agent's pane:\n1. Nudge the agent: `\"You hit a GitHub secondary rate limit (403). Stop all API writes. Wait 2 minutes, then resume with sleep 3 between each thread reply.\"`\n2. Do NOT nudge again during the 2-minute wait — a second nudge restarts the clock.\n\nAdd this to agent briefings when there are >20 unresolved threads:\n> Post replies with `sleep 3` between each reply. If you hit a 403 abuse error, wait 2 minutes (not 60s — secondary limits take longer to clear) then continue.\n\n## Key rules\n\n1. **Scripts do all the heavy lifting** — don't reimplement their logic inline in this file\n2. **Never ask the user to pick a worktree** — auto-assign from `find-spare.sh` output\n3. **Never restart a running agent** — only restart on `idle` kicks (foreground is a shell)\n4. **Auto-dismiss settings dialogs** — if \"Enter to confirm\" appears, send Down+Enter\n5. **Always `--permission-mode bypassPermissions`** on every spawn\n6. **Escalate after 3 kicks** — mark `escalated`, surface to user\n7. **Atomic state writes** — always write to `.tmp` then `mv`\n8. **Never approve destructive commands** outside the worktree scope — when in doubt, escalate\n9. **Never recycle without verification** — `verify-complete.sh` must pass before recycling\n10. **No TASK.md files** — commit risk; use state file + `gh pr view` for agent context persistence\n11. **Re-brief stalled agents** — read objective from state file + `gh pr view`, send via tmux\n12. **ORCHESTRATOR:DONE is a signal to verify, not to accept** — always run `verify-complete.sh` and check CI run timestamp before recycling\n13. **Protected worktrees** — never use the worktree hosting the skill scripts as a spare\n14. **Images via file path** — save screenshots to `/tmp/orchestrator-context-<ts>.png`, pass path in objective; agents read with the `Read` tool\n15. **Split send-keys** — always separate text and Enter with `sleep 0.3` between calls for long strings\n16. **Poll ALL windows from state file** — never hardcode window count. Derive active windows dynamically: `jq -r '.agents[] | select(.state | test(\"running|idle|stuck\")) | .window' ~/.claude/orchestrator-state.json`. If you added a window mid-session outside spawn-agent.sh, add it to the state file immediately.\n20. **Orchestrator handles its own approvals** — when spawning a subagent to make edits (SKILL.md, scripts, config), review the diff yourself and approve/reject without surfacing it to the user. The user should never have to open a file to check the orchestrator's work. Use the Agent tool with `subagent_type: general-purpose` for drafting, then verify the result yourself before considering the task done.\n17. **Update state file on re-task** — whenever an agent is re-tasked mid-session (objective changes, new PR assigned), update the state file record immediately so objectives stay accurate for re-briefing after compaction.\n18. **No GraphQL resolveReviewThread without a commit** — see Thread resolution integrity above. This is rule #1 for pr-address work.\n19. **Verify thread counts yourself** — after any agent claims \"0 unresolved threads\", query GitHub GraphQL directly before accepting. Never trust the agent's self-report.\n","frontmatter":{"name":"orchestrate","description":"Meta-agent supervisor that manages a fleet of Claude Code agents running in tmux windows. Auto-discovers spare worktrees, spawns agents, monitors state, kicks idle agents, approves safe confirmations, and recycles worktrees when done. TRIGGER when user asks to supervise agents, run parallel tasks, manage worktrees, check agent status, or orchestrate parallel work.","user-invocable":true,"argument-hint":"any free text — e.g. 'start 3 agents on X Y Z', 'show status', 'add task: implement feature A', 'stop', 'how many are free?'","metadata":{"author":"autogpt-team","version":"6.0.0"}},"isInternal":false,"tokens":9304,"sizeBytes":37102},{"name":"aggregate_baseline.json","path":".claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json)","tokens":300,"sizeBytes":262},{"name":"aggregate_candidate.json","path":".claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json)","tokens":300,"sizeBytes":264},{"name":"error_400.json","path":".claude/skills/perf-compare/scripts/fixtures/error_400.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/error_400.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/error_400.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/error_400.json)","tokens":300,"sizeBytes":244},{"name":"project_builder.json","path":".claude/skills/perf-compare/scripts/fixtures/project_builder.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/project_builder.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/project_builder.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/project_builder.json)","tokens":300,"sizeBytes":256},{"name":"samples_baseline_page1.json","path":".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json)","tokens":300,"sizeBytes":270},{"name":"samples_baseline_page2.json","path":".claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json)","tokens":300,"sizeBytes":270},{"name":"samples_candidate_page1.json","path":".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json)","tokens":300,"sizeBytes":272},{"name":"samples_candidate_page2.json","path":".claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json","title":"Fixtures Skill","category":"anthropic-skill","format":"json","content":"# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json)","tokens":300,"sizeBytes":272},{"name":"SKILL.md","path":".claude/skills/perf-compare/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/SKILL.md","title":"perf-compare","category":"anthropic-skill","format":"markdown","content":"---\nname: perf-compare\ndescription: \"Shared measurement recipe for AutoGPT performance work. Compares a baseline and a candidate in Sentry (two releases, two environments such as preview pr-<n> deploys, or two time windows) per transaction: count, p50/p95/p99, failure rate, and a trace-clustered bootstrap verdict of faster / slower / no detectable change / not enough data, with exit codes agents can branch on. Works for span duration, web vitals (measurements.lcp) or any numeric span field. TRIGGER when an agent needs to show a change made the backend or frontend faster or slower, compare a preview against a control or dev, check a release after merge on dev or prod, or check for regressions.\"\nuser-invocable: true\nargument-hint: \"[what to compare, e.g. 'builder release OLD vs NEW on prod' or 'pr-123 vs control pr-124 on /api/library/agents']\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# perf-compare: one way to measure performance\n\nEvery agent that tunes speed or stability measures with this recipe, so results are comparable across agents and noise is not reported as a win. Sentry is the store: org `significant-gravitas`, API `https://us.sentry.io`, tracing at 100%. `scripts/perf_compare.py` (Python 3.8+, standard library only) runs the queries and the statistics.\n\n```bash\nS=$(git rev-parse --show-toplevel)/.claude/skills/perf-compare/scripts/perf_compare.py\npython3 \"$S\" --help\n```\n\n## When to use it\n\n- Before claiming a change made something faster or slower, on a preview or after merge.\n- After merge, to check a win holds on dev and then prod (release against release).\n- To check a change did not regress other transactions (run without `--transaction`).\n- For any numeric field: `span.duration` (default), web vitals on builder pageloads (`measurements.lcp`), LLM token counts (`gen_ai.usage.input_tokens`), or custom attributes (`tags[name,number]`).\n\nIt is not for debugging one slow request (open the trace) or for error triage.\n\n## Token\n\nThe script reads `SENTRY_AUTH_TOKEN` from the environment and nowhere else. It needs a read-only token with exactly `org:read`, `project:read` and `event:read`: either a user auth token (Settings > Account > API > Auth Tokens) or an internal integration with Read on Organization, Project and Issue & Event. Organization auth tokens (`sntrys_...`) carry only `org:ci` and get HTTP 403 on these endpoints. A missing token exits with code 5 and these instructions. `--print-queries` needs no token.\n\n## Where things are in Sentry\n\n| Project | What it holds | Environments | `release` |\n|---|---|---|---|\n| `autogpt-server` | Backend: FastAPI routes (`/api/library/agents`, `/api/chat/sessions/{session_id}/stream`) and internal RPC services (`/update_node_execution_status`) | `app:prod-behave:cloud`, `app:dev-behave:cloud` | git sha once the backend sets it; check first |\n| `builder` | Frontend (Next.js on Vercel): `http.server` routes (`GET /api/proxy/[...path]`), `middleware GET`, `serverAction/*`, browser `pageload` and `navigation` with web vitals | `app:prod-behave:cloud` is prod; `app:preview-behave:cloud` is dev **and** every Vercel preview | git sha |\n| `autogpt-server-previews`, `builder-previews` | `!deploy` previews | `pr-<n>` | git sha |\n\nCheck a selector has data before you measure:\n\n```text\nsearch_events  projectSlug=<project>  dataset=spans  query=\"is_transaction:true\"\n               fields=[\"environment\",\"release\",\"count()\"]  sort=\"-count()\"  period=\"7d\"\n```\n\nIf `release` comes back null for a project, select by time window instead of release. If `autogpt-server-previews` has no `pr-<n>` events, that preview's backend is reporting into `autogpt-server` as `app:dev-behave:cloud`, mixed with dev traffic, and cannot be measured separately.\n\n## Choosing baseline and candidate\n\nBoth sides take `KEY=VALUE` pairs: `project=`, `env=`, `release=`, `query=` (extra Sentry filter), and time as `window=START/END` (repeatable), `start=`/`end=`, or `period=24h`. Shared defaults come from `--project`, `--env`, `--query` and `--period`/`--start`/`--end`/`--window`. A side's own `project=`, `env=` or `query=` wins over the shared value (`env=` with nothing after it clears it). Any time key on a side (`window=`, `start=`/`end=` or `period=`) replaces all of the shared time flags for that side. Times are UTC ISO 8601; without a time anywhere, both sides use the last 7 days.\n\n**1. After merge: release against release (dev or prod).** Same project and environment, previous release against new release, in windows of equal length that cover the same hours of the day: the 24 hours before the deploy against the 24 hours after, or 7 days against 7 days for quiet routes. Always filter by release when there is one. Old releases keep sending events for days (open tabs, Vercel skew protection), so a time window alone mixes releases.\n\n```bash\npython3 \"$S\" --project builder --env app:prod-behave:cloud \\\n  --baseline release=OLD_SHA window=2026-10-10T14:00:00Z/2026-10-11T14:00:00Z \\\n  --candidate release=NEW_SHA window=2026-10-11T14:00:00Z/2026-10-12T14:00:00Z\n```\n\n**2. On a preview: candidate preview against a control preview.** Deploy the change with `!deploy`, and deploy a control (the dev commit the change branches from, with no changes) the same way. Each gets its own namespace and an empty branch database, so the two are alike. Drive the same load at both, either together in one window or interleaved A B B A (control, candidate, candidate, control; about 10 minutes each) so drift cancels:\n\n```bash\npython3 \"$S\" --project autogpt-server-previews --transaction '/api/store/agents' \\\n  --baseline env=pr-CONTROL window=T0/T1 window=T3/T4 \\\n  --candidate env=pr-CANDIDATE window=T1/T2 window=T2/T3\n```\n\nMeasuring the candidate preview against `app:dev-behave:cloud` at dev HEAD in the same window is cheaper but weaker: dev has a full database, real traffic and different pods. Use it as a rough read, not as proof.\n\n**3. No release tag: before against after.** Equal windows either side of the deploy. Other deploys inside those windows also change things, so say so in your report.\n\n**4. A/A check before trusting a win.** Compare a selector with itself over two windows, for example the same release yesterday and today. It should say `no detectable change`. If it does not, your windows differ too much (hour of day, weekday, traffic mix), and so will your real comparison.\n\n## How much data\n\n- The script gives no p50 or p95 verdict unless each side has `--min-count` samples (default 200) **from** `--min-traces` distinct traces (default 30). At 200, p95 rests on 10 values above it. p99 is shown but never judged. The verdict cell says which minimum was missed. The failure-rate test works from Sentry's event counts instead, and needs `--min-count` events per side.\n- It samples up to `--sample-cap` values per side per transaction (default 1000, at most 20000), paging 100 at a time. A cap below `--min-count` is refused.\n- For a claim about one transaction, name it with `--transaction` and raise `--sample-cap` to 2000–5000. Naming it also shrinks the multiple-comparison correction (see below).\n- Correlated spans count once: the bootstrap resamples whole traces, and the trace minimum stops a few busy traces (a page polling an endpoint) from passing as a big sample. The table shows samples and distinct traces. The effective sample is closer to the trace count than the span count. In A/A tests on clustered data, resampling spans one at a time called 38–44% of identical pairs a change, against 2–5% when resampling by trace.\n- Drive at least 1000 requests per side per transaction you care about. Wait a few minutes after load stops before measuring, so Sentry has the last events.\n\n## Driving load on a preview\n\nA preview's backend is `https://pr-<N>-server.agpt.co` (REST under `/api`, health at `/health`), and its frontend is `https://autogpt-pr-<N>.vercel.app`.\n\nThe k6 tests are in `autogpt_platform/backend/load-tests/`; its `README.md` covers every test and setting. Run them on your own machine: `./k6.sh` runs k6 in Docker (`grafana/k6`), so nothing else needs installing, and a local run costs nothing beyond the load on the target. Do not run them on Grafana Cloud k6. It bills every virtual-user hour, so `./k6.sh` refuses cloud runs unless they are explicitly opted into, and the README reserves them for rare runs the team has agreed to. `K6_ENVIRONMENT=PREVIEW` with `PR_NUMBER=<N>` targets a preview:\n\n```bash\ncd autogpt_platform/backend/load-tests\nexport K6_ENVIRONMENT=PREVIEW PR_NUMBER=<N>\nVUS=5 DURATION=10m RAMP_UP=30s RAMP_DOWN=30s ./k6.sh run tests/api/core-api-test.js\n```\n\n- **Authentication needs no setup on a preview.** The authenticated tests (`tests/api/*`, `tests/basic/*`, `tests/marketplace/library-access-test.js`, `tests/comprehensive/platform-journey-test.js`) sign up throwaway `loadtest-*@previews.agpt.co` accounts in `setup()`, once per run, and use the Better Auth tokens the preview's frontend issues for them. `LOAD_TEST_ACCOUNT_COUNT` sets how many (default 5). `tests/marketplace/public-access-test.js` needs no account at all.\n- **To run as specific accounts**, such as the seeded `preview-*@previews.agpt.co` personas, set `LOAD_TEST_ACCOUNTS` to a JSON array of `{\"email\", \"password\"}`. The tests sign each one in once per run. Dev has no throwaway sign-up, so it always needs `LOAD_TEST_ACCOUNTS`.\n- **Note the run's UTC window** (`date -u +%FT%TZ` before and after) for the baseline and candidate windows above, and drive the same test, VUs and duration at both sides.\n- **Previews start with an empty database.** Store and library lists return nothing, so costs that grow with data (N+1 queries, scans) do not show. Seed data through the API first, or confirm on dev and prod after merge.\n- None of the k6 tests open copilot chats. To measure copilot turns, run chats against the preview yourself, or measure on dev and prod traffic.\n- The repository's evals (`backend/copilot/eval`, `backend/copilot/capabilities/eval`, `poetry run expert-style-eval`, `poetry run brain-dump-eval`) are offline quality and cost harnesses. They send no traffic to a deploy.\n- After merge, real traffic on dev and prod is the load.\n\n## Reading the verdict\n\nThe markdown output (default) or `--json` reports two things for each transaction.\n\n- **Sentry aggregates over every matching event:** count, p50, p95, p99 and failure rate. These are exact for the windows.\n- **A verdict for p50 and p95:**\n  - The interval is a bootstrap CI of candidate minus baseline from the raw samples, resampling whole traces.\n  - It is Bonferroni-adjusted so that every interval in the run holds together at `--confidence` (95%). Two transactions means four intervals at 98.75% each. The plain 95% interval is in the JSON as `ci`.\n  - Resamples are raised automatically until each tail of the adjusted interval holds 20 draws, up to 50000. For example, 4000 becomes 8000 for five transactions. At the 50000 cap a run still goes ahead with as few as 10 draws per tail. A run that would leave fewer than 10, because too many transactions are compared, is refused before it starts (exit 2). The JSON reports the number used as `settings.resamples`.\n  - `faster` or `slower` needs the adjusted interval to exclude zero, and **both** the sample change and Sentry's all-events change to be at least `--min-effect` (5%) in that direction. Anything else is `no detectable change`. Below `--min-count` samples or `--min-traces` traces it is `not enough data`.\n  - The transaction takes its worst percentile: `slower` if either is slower, else `faster` if either is faster.\n- **Failure rate:** Fisher's exact test on the failure counts behind Sentry's `failure_rate()`. It is exact because a normal approximation is wrong with a handful of failures: 0 of 1000 against 4 of 1000 passes a z-test, but Fisher gives p = 0.12.\n  - The significance level is Bonferroni-adjusted over the K transactions that have `--min-count` events on both sides. That is one test per transaction, not two.\n  - `higher` needs significance and a rise of at least `--min-failure-delta` (0.1 percentage points). It counts as a regression even when the transaction got faster, because requests that fail fast look like a speedup.\n  - The test treats every event as independent, and `--min-traces` does not apply to it. When failures come from a few traces (one broken page retrying), it overstates significance. Check `sample_traces` and the failing traces in Sentry before acting on a failure-rate result alone.\n\n`no detectable change` means \"not distinguishable from noise with this much data\". It does not mean \"no change\". Check the interval widths and trace counts before concluding anything.\n\n**A win counts** only when all of these hold:\n\n- you named the transaction with `--transaction` before measuring;\n- the verdict is `faster`;\n- the failure rate is not `higher`;\n- it holds again on a second, independent window or run.\n\nA run over the top transactions is for spotting regressions and leads, not for claiming wins.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | faster: a compared transaction got faster and nothing regressed |\n| 1 | slower or more failures: at least one compared transaction regressed |\n| 2 | usage error: bad arguments (including a `--region-url` that is not https, `--timeout` ≤ 0, or `--sample-cap` below `--min-count`), unknown project, or Sentry rejected the query (HTTP 400) |\n| 3 | no detectable change |\n| 4 | not enough data: no compared transaction met `--min-count` and `--min-traces` on both sides |\n| 5 | auth: `SENTRY_AUTH_TOKEN` missing, or Sentry rejected it (401/403) |\n| 6 | Sentry API or network error after retries, or any unexpected error; never read as a regression |\n\nWith `--json`, the fields to branch on are:\n\n- `overall.verdict`, `overall.exit_code` and `overall.reasons`;\n- per transaction in `transactions[]`:\n  - `verdict`, `note` (why there is not enough data), `regression`;\n  - `p50`/`p95`: `baseline`, `candidate`, `diff`, `rel`, `sentry_rel`, `ci`, `ci_adjusted`, `verdict`;\n  - `failure_rate`: `baseline`, `candidate`, `diff`, `p_value`, `verdict`;\n  - `baseline`/`candidate`: `count`, `p50`, `p95`, `p99`, `failure_rate`, `sample_n`, `sample_traces`;\n- `settings`, holding the values actually used: `sample_cap`, `resamples`, `comparisons`, `failure_comparisons`, `adjusted_confidence`, `min_count`, `min_traces`, `min_effect`, `seed`.\n\nErrors in `--json` mode print `{\"error\": {\"type\", \"message\"}, \"exit_code\"}`, where `type` is `usage`, `auth`, `api` or `internal`.\n\n## The exact queries\n\nAll requests are `GET https://us.sentry.io/api/0/organizations/significant-gravitas/events/` with `dataset=spans` and `project=<numeric id>`. The id comes from `GET /api/0/projects/significant-gravitas/<slug>/`. `start` and `end` (UTC) span the side's windows; the query's timestamp filter pins each window exactly. `F` is `--field`.\n\n1. **Aggregate per transaction**, one request per side:\n   - `field=transaction`, `count()`, `p50(F)`, `p95(F)`, `p99(F)`, `failure_rate()`\n   - `sort=-count()`, `per_page=100`\n2. **Raw sample**, per side per compared transaction:\n   - `field=F`, `trace`, `id`\n   - `sort=id`, `per_page=100`, paged with the `cursor` from the `Link` header (`rel=\"next\"; results=\"true\"`) until `--sample-cap` rows.\n   - Span ids are random, so the first N rows by `id` are a uniform random sample of the windows. Sorting by `-timestamp` instead returns the latest burst, often a few traces.\n\nBoth use this query string:\n\n```text\nis_transaction:true environment:\"ENV\" release:\"SHA\" [has:F] [(EXTRA QUERY)]\n  [transaction:\"T\" | (transaction:\"A\" OR transaction:\"B\")]\n  timestamp:>=2026-10-11T14:00:00Z timestamp:<2026-10-12T14:00:00Z\n```\n\nSeveral windows become `(timestamp:>=S1 timestamp:<E1 OR timestamp:>=S2 timestamp:<E2)`. `has:F` appears for any field other than `span.duration`.\n\n`python3 \"$S\" ... --print-queries` prints the exact REST URL and MCP arguments for an invocation, without a token.\n\n**With only the Sentry MCP**, use `search_events` with `dataset: \"spans\"`, or `search_traces` without `dataset`:\n\n- Pass `projectSlug`, the same `query`, `fields` and `sort`.\n- Pass a `period` long enough to cover the windows, such as `\"50h\"`; the timestamp filter does the exact cut. The argument is `period`, not `statsPeriod`, and an unknown key silently falls back to 14 days.\n- Pass `limit` ≤ 100.\n- Confirm the \"Executed Search\" block in the reply shows your query unchanged.\n- The MCP cannot page. For more than 100 samples, split each window into sub-windows, get `count()` for each, and take the first rows by `id` from each in proportion to its count.\n\n## Field names checked against live data\n\nChecked through the Sentry MCP on `builder` and `autogpt-server`.\n\n- **Filters:**\n  - `is_transaction:true` and `transaction:\"…\"`.\n  - `environment:\"…\"`: quote it, the values contain colons.\n  - `release:\"<sha>\"`.\n  - `span.op:` with `pageload`, `navigation`, `http.server`, `http.server.middleware` or `function.server_action`.\n  - `span.status:[…]` and `!span.status:[…]`.\n  - `has:measurements.lcp` and `has:tags[gen_ai.usage.input_tokens,number]`.\n  - `timestamp:>=…Z timestamp:<…Z` and parenthesised `OR` groups.\n- **Aggregates:**\n  - `count()`.\n  - `p50()`, `p75()`, `p95()` and `p99()`, of `span.duration` (ms), `measurements.lcp`, `measurements.fcp`, `measurements.ttfb`, `measurements.cls`, `gen_ai.usage.input_tokens` or `tags[gen_ai.usage.input_tokens,number]`.\n  - `failure_rate()`: the share of spans whose `span.status` is not `ok`, `cancelled` or `unknown`. It reconciles exactly with `count()` and `!span.status:[ok,cancelled,unknown]`.\n- **Sample fields:** `span.duration`, `trace`, `id`, `timestamp`, `release`. Sorting works by `id`, `-id` and `-timestamp`.\n- **`span.status` values seen:** `ok`, `internal_error`, `resource_exhausted`, `cancelled`, `deadline_exceeded`, `invalid_argument`, `not_found`, `already_exists`.\n- **Rejected:** wildcards on `id` such as `id:0*`; span ids must be full 16-character hex.\n\n## Pitfalls\n\n- **Environment names collide.** Builder's dev deploy and every Vercel preview share `app:preview-behave:cloud`. Tell them apart with `release`.\n- **Proxy routes measure the backend.** `GET /api/proxy/[...path]` mostly measures the backend behind it, not the frontend release, and a few polling pages contribute most of its spans.\n- **Middleware durations are quantized** to about 1 ms. A one-step move in p95 reads as +100%; look at the absolute change.\n- **Long-lived transactions measure session length.** `/ws` and `/api/chat/sessions/{session_id}/stream` last minutes to an hour, not request latency. Compare per-turn or per-node transactions instead.\n- **Weekday and hour of day move latency.** Saturday-to-Sunday against Sunday-to-Monday is not a fair pair for a release comparison. Use full days, the same hours, and the A/A check.\n- **Do not rerun until it says faster.** Repeating runs, or trying many transactions and reporting the best, turns noise into wins. Decide the transaction, windows and settings first, then run once and replicate once.\n\n## Tests\n\n```bash\npython3 -m unittest discover -s .claude/skills/perf-compare/scripts\n```\n\nThe tests run offline in a few seconds, against the synthetic Sentry responses in `scripts/fixtures/`, served by a fake transport or by a local `http.server`. They cover:\n\n- query building, time parsing and option validation;\n- cursor pagination, retries, and real-socket errors;\n- the percentile, bootstrap and Fisher maths with fixed seeds;\n- seeded A/A calibration on independent and trace-clustered data;\n- the verdict rules, every exit code, and the missing-token error.\n","frontmatter":{"name":"perf-compare","description":"Shared measurement recipe for AutoGPT performance work. Compares a baseline and a candidate in Sentry (two releases, two environments such as preview pr-<n> deploys, or two time windows) per transaction: count, p50/p95/p99, failure rate, and a trace-clustered bootstrap verdict of faster / slower / no detectable change / not enough data, with exit codes agents can branch on. Works for span duration, web vitals (measurements.lcp) or any numeric span field. TRIGGER when an agent needs to show a change made the backend or frontend faster or slower, compare a preview against a control or dev, check a release after merge on dev or prod, or check for regressions.","user-invocable":true,"argument-hint":"[what to compare, e.g. 'builder release OLD vs NEW on prod' or 'pr-123 vs control pr-124 on /api/library/agents']","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":5093,"sizeBytes":19700},{"name":"SKILL.md","path":".claude/skills/pr-address/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/pr-address/SKILL.md","title":"pr-address","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-address\ndescription: Address PR review comments and loop until CI green and all comments resolved. TRIGGER when user asks to address comments, fix PR feedback, respond to reviewers, or babysit/monitor a PR.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Address\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nUnderstand the **Why / What / How** before addressing comments — you need context to make good fixes:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\n> If GraphQL is rate-limited, `gh pr view` fails. See [GitHub rate limits](#github-rate-limits) for REST fallbacks.\n\n## Optional: trigger the review bot\n\n**Only relevant if the PR has no review yet.** If the fetch steps below turn up no reviews and no inline threads, there is nothing to address *yet* — that is not the same as being done. You can summon the repo's review bot yourself:\n\n```bash\ngh pr comment {N} --body \"/review\"\n```\n\nThis is an **option, not a required step**. Skip it whenever the PR already has reviews (bot or human) — those are what you are here to address. See the `open-pr` skill's \"Review workflow\" step for the same trigger in the PR-creation flow.\n\n`autogpt-pr-reviewer[bot]` typically responds within ~30 minutes. **Poll for it rather than sleeping** — the loop in [Polling for CI + new comments](#polling-for-ci--new-comments) already re-checks every source every 30 seconds, so run that loop and react the moment the review lands. Note the `id` and `created_at` of the `/review` comment you posted; [Waiting on a requested review](#waiting-on-a-requested-review) needs them for the exit condition.\n\n## Fetch comments (all sources)\n\n### 1. Inline review threads — GraphQL (primary source of actionable items)\n\n> ⚠️ **WARNING — PAGINATE ALL PAGES BEFORE ADDRESSING ANYTHING**\n>\n> `reviewThreads(first: 100)` returns at most 100 threads per page AND returns threads **oldest-first**. On a PR with many review cycles (e.g. 373 threads), the oldest 100–200 threads are from past cycles and are **all already resolved**. Filtering client-side with `select(.isResolved == false)` on page 1 therefore yields **0 results** — even though pages 2–4 contain many unresolved threads from recent review cycles.\n>\n> **This is the most common failure mode:** agent fetches page 1, sees 0 unresolved after filtering, stops pagination, reports \"done\" — while hundreds of unresolved threads sit on later pages.\n>\n> One observed PR had 142 total threads: page 1 returned 0 unresolved (all old/resolved), while pages 2–3 had 111 unresolved. Another with 373 threads across 4 pages also had page 1 entirely resolved.\n>\n> **The rule: ALWAYS paginate to `hasNextPage == false` regardless of the per-page unresolved count. Never stop early because a page returns 0 unresolved.**\n\n**Step 1 — Fetch total count and sanity-check the newest threads:**\n\n```bash\n# Get total count and the newest 100 threads (last: 100 returns newest-first)\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n      newest: reviewThreads(last: 100) {\n        nodes { isResolved }\n      }\n    }\n  }\n}' | jq '{ total: .data.repository.pullRequest.reviewThreads.totalCount, newest_unresolved: [.data.repository.pullRequest.newest.nodes[] | select(.isResolved == false)] | length }'\n```\n\nIf `total > 100`, you have multiple pages — you **must** paginate all of them regardless of what `newest_unresolved` shows. The `last: 100` check is a sanity signal only; the full loop below is mandatory.\n\n**Step 2 — Collect all unresolved thread IDs across all pages:**\n\n```bash\n# Accumulate all unresolved threads — loop until hasNextPage == false\nCURSOR=\"\"\nALL_THREADS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            id\n            isResolved\n            path\n            line\n            comments(last: 1) {\n              nodes { databaseId body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  # Append unresolved nodes from this page\n  PAGE_THREADS=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)]')\n  ALL_THREADS=$(echo \"$ALL_THREADS $PAGE_THREADS\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\n\n# Reverse so newest threads (last pages) are addressed first — GitHub returns oldest-first\n# and the most recent review cycle's comments are the ones blocking approval.\nALL_THREADS=$(echo \"$ALL_THREADS\" | jq 'reverse')\n\necho \"Total unresolved threads: $(echo \"$ALL_THREADS\" | jq 'length')\"\necho \"$ALL_THREADS\" | jq '[.[] | {id, path, line, body: .comments.nodes[0].body[:200]}]'\n```\n\n**Step 3 — Address every thread in `ALL_THREADS`, then resolve.**\n\nOnly after this loop completes (all pages fetched, count confirmed) should you begin making fixes.\n\n> **Why reverse?** GraphQL returns threads oldest-first and exposes no `orderBy` option. A PR with 373 threads has ~4 pages; threads from the latest review cycle land on the last pages. Processing in reverse ensures the newest, most blocking comments are addressed first — the earlier pages mostly contain outdated threads from prior cycles.\n\n**Filter to unresolved threads only** — skip any thread where `isResolved: true`. `comments(last: 1)` returns the most recent comment in the thread — act on that; it reflects the reviewer's final ask. Use the thread `id` (Relay global ID) to track threads across polls.\n\n> If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the REST fallback (flat comment list — no thread grouping or `isResolved`).\n\n### 2. Top-level reviews — REST (MUST paginate)\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits or outages. Continue polling reviews normally even when GraphQL is exhausted.**\n\n**CRITICAL — always `--paginate`.** Reviews default to 30 per page. PRs can have 80–170+ reviews (mostly empty resolution events). Without pagination you miss reviews past position 30 — including `autogpt-pr-reviewer[bot]`'s structured review which is typically posted after several CI runs and sits well beyond the first page.\n\nTwo things to extract:\n- **Overall state**: look for `CHANGES_REQUESTED` or `APPROVED` reviews.\n- **Actionable feedback**: non-empty bodies only. Empty-body reviews are thread-resolution events — they indicate progress but have no feedback to act on.\n\n**Where each reviewer posts:**\n- `autogpt-pr-reviewer[bot]` — posts detailed structured reviews (\"Blockers\", \"Should Fix\", \"Nice to Have\") as **top-level reviews**. Not present on every PR. Address ALL items.\n- `sentry[bot]` — posts bug predictions as **inline threads**. Fix real bugs, explain false positives.\n- `coderabbitai[bot]` — posts summaries as **top-level reviews** AND actionable items as **inline threads**. Address actionable items.\n- Human reviewers — can post in any source. Address ALL non-empty feedback.\n\n### 3. PR conversation comments — REST\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits.**\n\nMostly contains: bot summaries (`coderabbitai[bot]`), CI/conflict detection (`github-actions[bot]`), and author status updates. Scan for non-empty messages from non-bot human reviewers that aren't the PR author — those are the ones that need a response.\n\n**Slash-command trigger comments are never actionable items — skip them.** A comment whose entire body is a slash command (`/review` being the common one) exists to summon the review bot; it is not a request directed at you. Do not reply to it, do not treat it as feedback, and do not count it as a \"new comment\" that restarts the loop. The non-author filter above happens to hide the usual case where the PR author posted it, but that is incidental — a maintainer can post `/review` on someone else's PR, and it must still be skipped. The actionable content is the review it produces, which arrives as a top-level review and/or inline threads, not as a conversation comment.\n\n## For each unaddressed comment\n\n**CRITICAL: The only valid sequence is fix → commit → push → reply → resolve. Never resolve a thread without a real code commit.**\n\nResolving a thread via `resolveReviewThread` without an actual fix is the most common failure mode — it makes unresolved counts drop without any real change, producing a false \"done\" signal. If the issue was genuinely a false positive (no code change needed), reply explaining why and then resolve. Otherwise:\n\nAddress comments **one at a time**: fix → commit → push → inline reply → resolve.\n\n1. Read the referenced code, make the fix (or reply explaining why it's not needed)\n2. Commit and push the fix\n3. Reply **inline** (not as a new top-level comment) referencing the fixing commit — this is what resolves the conversation for bot reviewers (coderabbitai, sentry):\n\nUse a **markdown commit link** so GitHub renders it as a clickable reference. Always get the full SHA with `git rev-parse HEAD` **after** committing — never copy a SHA from a previous commit or hardcode one:\n\n```bash\nFULL_SHA=$(git rev-parse HEAD)\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies \\\n  -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): <description>\"\n```\n\n| Comment type | How to reply |\n|---|---|\n| Inline review (`pulls/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n| Conversation (`issues/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n\n### What counts as a valid resolution\n\nOnly two situations justify calling `resolveReviewThread`:\n\n1. **Real code fix**: you changed the code, committed + pushed, and replied with the SHA. The commit diff must actually address the concern — not just touch the same file.\n2. **Genuine false positive**: the reviewer's concern does not apply to this code, and you can give a specific technical reason (e.g. \"Not applicable — `sdk_cwd` is pre-validated by `_make_sdk_cwd()` which applies normpath + prefix assertion before reaching this point\").\n\n**Anti-patterns that look resolved but aren't — never do these:**\n- `\"Accepted, tracked as follow-up\"` — a deferral, not a fix. The concern is still open. Do not resolve.\n- `\"Acknowledged\"` or `\"Same as above\"` — these are acknowledgements, not fixes. Do not resolve.\n- `\"Fixed in abc1234\"` where `abc1234` is a commit that doesn't actually change the flagged line/logic — dishonest. Verify `git show abc1234 -- path/to/file` changes the right thing before posting.\n- Resolving without replying — the reviewer never sees what happened.\n\nWhen in doubt: if a code change is needed, make it. A deferred issue means the thread stays open until the follow-up PR is merged.\n\n## Codecov coverage\n\nCodecov patch target is **80%** on changed lines. Checks are **informational** (not blocking) but should be green.\n\n### Running coverage locally\n\n**Backend** (from `autogpt_platform/backend/`):\n```bash\npoetry run pytest -s -vv --cov=backend --cov-branch --cov-report term-missing\n```\n\n**Frontend** (from `autogpt_platform/frontend/`):\n```bash\npnpm vitest run --coverage\n```\n\n### When codecov/patch fails\n\n1. Find uncovered files: `git diff --name-only $(gh pr view --json baseRefName --jq '.baseRefName')...HEAD`\n2. For each uncovered file — extract inline logic to `helpers.ts`/`helpers.py` and test those (highest ROI). Colocate tests as `*_test.py` (backend) or `__tests__/*.test.ts` (frontend).\n3. Run coverage locally to verify, commit, push.\n\n## Format and commit\n\nAfter fixing, format the changed code:\n\n- **Backend** (from `autogpt_platform/backend/`): `poetry run format`\n- **Frontend** (from `autogpt_platform/frontend/`): `pnpm format && pnpm lint && pnpm types`\n\nIf API routes changed, regenerate the frontend client:\n```bash\ncd autogpt_platform/backend && poetry run rest &\nREST_PID=$!\ntrap \"kill $REST_PID 2>/dev/null\" EXIT\nWAIT=0; until curl -sf http://localhost:8006/health > /dev/null 2>&1; do sleep 1; WAIT=$((WAIT+1)); [ $WAIT -ge 60 ] && echo \"Timed out\" && exit 1; done\ncd ../frontend && pnpm generate:api:force\nkill $REST_PID 2>/dev/null; trap - EXIT\n```\nNever manually edit files in `src/app/api/__generated__/`.\n\nThen commit and **push immediately** — never batch commits without pushing. Each fix should be visible on GitHub right away so CI can start and reviewers can see progress.\n\n**Never push empty commits** (`git commit --allow-empty`) to re-trigger CI or bot checks. When a check fails, investigate the root cause (unchecked PR checklist, unaddressed review comments, code issues) and fix those directly. Empty commits add noise to git history.\n\nFor backend commits in worktrees: `poetry run git commit` (pre-commit hooks).\n\n## Coverage\n\nCodecov enforces patch coverage on new/changed lines — new code you write must be tested. Before pushing, verify you haven't left new lines uncovered:\n\n```bash\ncd autogpt_platform/backend\npoetry run pytest --cov=. --cov-report=term-missing {path/to/changed/module}\n```\n\nLook for lines marked `miss` — those are uncovered. Add tests for any new code you wrote as part of addressing comments.\n\n**Rules:**\n- New code you add should have tests\n- Don't remove existing tests when fixing comments\n- If a reviewer asks you to delete code, also delete its tests, but verify coverage hasn't dropped on remaining lines\n\n## The loop\n\n```text\naddress comments → format → commit → push\n→ wait for CI (while addressing new comments) → fix failures → push\n→ re-check comments after CI settles\n→ repeat until: all comments addressed AND CI green AND no new comments arriving\n```\n\n### Polling for CI + new comments\n\nAfter pushing, poll for **both** CI status and new comments in a single loop. Do not use `gh pr checks --watch` — it blocks the tool and prevents reacting to new comments while CI is running.\n\n> **Note:** `gh pr checks --watch --fail-fast` is tempting but it blocks the entire Bash tool call, meaning the agent cannot check for or address new comments until CI fully completes. Always poll manually instead.\n\n**Polling loop — repeat every 30 seconds:**\n\n1. Check CI status:\n```bash\ngh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,name,link\n```\n   Parse the results: if every check has `bucket` of `\"pass\"` or `\"skipping\"`, CI is green. If any has `\"fail\"`, CI has failed. Otherwise CI is still pending.\n\n2. Check for merge conflicts:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json mergeable --jq '.mergeable'\n```\n   If the result is `\"CONFLICTING\"`, the PR has a merge conflict — see \"Resolving merge conflicts\" below. If `\"UNKNOWN\"`, GitHub is still computing mergeability — wait and re-check next poll.\n\n3. Check for new/changed comments (all three sources):\n\n   **Inline threads** — re-run the GraphQL query from \"Fetch comments\". For each unresolved thread, record `{thread_id, last_comment_databaseId}` as your baseline. On each poll, action is needed if:\n   - A new thread `id` appears that wasn't in the baseline (new thread), OR\n   - An existing thread's `last_comment_databaseId` has changed (new reply on existing thread)\n\n   **Conversation comments:**\n   ```bash\n   # Exclude slash-command triggers here, not just when reading — otherwise a\n   # `/review` (yours or a maintainer's) counts as a \"new comment\" and forces\n   # an address round over a comment with nothing to address.\n   gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate --slurp \\\n     | jq '[.[][] | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)]'\n   ```\n   Compare total count and newest `id` against baseline. Filter to non-empty, non-bot, non-author-update messages.\n\n   **Top-level reviews:**\n   ```bash\n   gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n   ```\n   Watch for new non-empty reviews (`CHANGES_REQUESTED` or `COMMENTED` with body). Compare total count and newest `id` against baseline.\n\n4. **React in this precedence order (first match wins):**\n\n| What happened | Action |\n|---|---|\n| Merge conflict detected | See \"Resolving merge conflicts\" below. |\n| Mergeability is `UNKNOWN` | GitHub is still computing mergeability. Sleep 30 seconds, then restart polling from the top. |\n| New comments detected | Address them (fix → commit → push → reply). After pushing, re-fetch all comments to update your baseline, then restart this polling loop from the top (new commits invalidate CI status). |\n| CI failed (bucket == \"fail\") | Get failed check links: `gh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,link --jq '.[] \\| select(.bucket == \"fail\") \\| .link'`. Extract run ID from link (format: `.../actions/runs/<run-id>/job/...`), read logs with `gh run view <run-id> --repo Significant-Gravitas/AutoGPT --log-failed`. Fix → commit → push → restart polling. |\n| CI green + no new comments, but a requested `/review` is unanswered | The bot is still working — quiet polls are exactly what that looks like. Keep polling; do **not** count these as the quiet polls below. See \"Waiting on a requested review\". |\n| CI green + no new comments | **Do not exit immediately.** Bots (coderabbitai, sentry) often post reviews shortly after CI settles. Continue polling for **2 more cycles (60s)** after CI goes green. Only exit after 2 consecutive green+quiet polls. |\n| CI pending + no new comments | Sleep 30 seconds, then poll again. |\n\n**The loop ends when:** CI fully green + all comments addressed + **2 consecutive polls with no new comments after CI settled** + no requested-but-unanswered `/review` (see below).\n\n### Waiting on a requested review\n\nIf a `/review` comment was posted on this PR and the bot has not answered it yet, the \"2 consecutive green+quiet polls\" exit is **premature** — a pending bot review looks identical to a quiet PR. Keep polling until one of these is true:\n\n- **The review arrived.** An answer is *either* a top-level review or an inline review comment from `autogpt-pr-reviewer[bot]`, whichever is later — an inline-only reply is still an answer, and counting only top-level reviews leaves the gate waiting after its threads have already been addressed. Once it lands, treat it as \"New comments detected\" and address it normally.\n- **The budget ran out.** Stop waiting **60 minutes** after the `/review` comment's `created_at`. Then exit per the normal rule and tell the user the requested review never arrived. Never hang indefinitely on a bot.\n\nRecompute both timestamps from the API on every poll rather than tracking\nelapsed time in a loop variable — a counter that some branch forgets to\nincrement is how this gate becomes an infinite wait.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\nA `/review` posted before this session still counts. Budget is **60 minutes**\nfrom `REQUESTED_AT`: an observed reply on this repo took 46 minutes, so 45\nwould have abandoned a review that was about to land. Enforce it in the same\npoll so the loop cannot wait forever:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\n**Do not post another `/review` while one is unanswered** — check this same\ncondition before triggering, or each round queues a duplicate request.\n\n### Resolving merge conflicts\n\n1. Identify the PR's target branch and remote:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json baseRefName --jq '.baseRefName'\ngit remote -v   # find the remote pointing to Significant-Gravitas/AutoGPT (typically 'upstream' in forks, 'origin' for direct contributors)\n```\n\n2. Pull the latest base branch with a 3-way merge:\n```bash\ngit pull {base-remote} {base-branch} --no-rebase\n```\n\n3. Resolve conflicting files, then verify no conflict markers remain:\n```bash\nif grep -R -n -E '^(<<<<<<<|=======|>>>>>>>)' <conflicted-files>; then\n  echo \"Unresolved conflict markers found — resolve before proceeding.\"\n  exit 1\nfi\n```\n\n4. Stage and push:\n```bash\ngit add <conflicted-files>\ngit commit -m \"Resolve merge conflicts with {base-branch}\"\ngit push\n```\n\n5. Restart the polling loop from the top — new commits reset CI status.\n\n## GitHub rate limits\n\nThree distinct rate limits exist — they have different causes, error shapes, and recovery times:\n\n| Error | HTTP code | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `{\"message\":\"API rate limit exceeded\"}` | 429 | Primary REST rate limit — 5000 calls/hr per user | Wait until `X-RateLimit-Reset` header timestamp |\n| `GraphQL: API rate limit already exceeded for user ID ...` | 403 on stderr, `gh` exits 1 | **GraphQL-specific** per-user limit — distinct from REST's 5000/hr and from the abuse secondary limit. Trips faster than REST because point costs per query. | Wait until the GraphQL window resets (typically ~1 hour from the first call in the window). REST still works — use fallbacks below. |\n\n**Prevention:** Add `sleep 3` between individual thread reply API calls. When posting >20 replies, increase to `sleep 5`.\n\n### Detection\n\nThe `gh` CLI surfaces the GraphQL limit on stderr with the exact string `GraphQL: API rate limit already exceeded for user ID <id>` and exits 1 — any `gh api graphql ...` **or** `gh pr view ...` call fails. Check current quota and reset time via the REST endpoint that reports GraphQL quota (this call is REST and still works whether GraphQL is rate-limited OR fully down):\n\n```bash\ngh api rate_limit --jq '.resources.graphql'   # { \"limit\": 5000, \"used\": 5000, \"remaining\": 0, \"reset\": 1729...}\n# Human-readable reset:\ngh api rate_limit --jq '.resources.graphql.reset' | xargs -I{} date -r {}\n```\n\nRetry when `remaining > 0`. If you need to proceed sooner, sleep 2–5 min and probe again — the limit is per user, not per machine, so other concurrent agents under the same token also consume it.\n\n### What keeps working\n\nWhen GraphQL is unavailable (rate-limited or outage):\n\n- **Keeps working (REST):** top-level reviews fetch, conversation comments fetch, all inline-comment replies, CI status (`gh pr checks`), and the `gh api rate_limit` probe.\n- **Degraded:** inline thread list — fall back to flat `/pulls/{N}/comments` REST, which drops thread grouping, `isResolved`, and Relay thread IDs. You still get comment bodies and the `databaseId` as `id`, enough to read and reply.\n- **Blocked:** `gh pr view`, the `resolveReviewThread` mutation, and any new `gh api graphql` queries — wait for the quota to reset.\n\n### Fall back to REST\n\n**PR metadata reads** — `gh pr view` uses GraphQL under the hood; use the REST pulls endpoint instead, which returns the full PR object:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.body'           # == --json body\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.base.ref'       # == --json baseRefName\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.mergeable'      # == --json mergeable\n```\n\nNote: REST `mergeable` returns `true|false|null`; GraphQL returns `MERGEABLE|CONFLICTING|UNKNOWN`. The `null` case maps to `UNKNOWN` — treat it the same (still computing; poll again).\n\n**Inline comments (flat list)** — no thread grouping or `isResolved`, but enough to read and reply:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate \\\n  | jq '[.[] | {id, path, line, user: .user.login, body: .body[:200], in_reply_to_id}]'\n```\n\nUse this degraded mode to make progress on the fix → reply loop, then return to GraphQL for `resolveReviewThread` once the rate limit resets.\n\n**Replies** — already REST-native (`/pulls/{N}/comments/{ID}/replies`); no change needed, use the same command as the main flow.\n\n**`resolveReviewThread`** — **no REST equivalent**; GitHub does not expose a REST endpoint for thread resolution. Queue the thread IDs needing resolution, wait for the GraphQL limit to reset, then run the resolve mutations in a batch (with `sleep 3` between calls, per the secondary-limit guidance).\n\n### Recovery from secondary rate limit (403 abuse)\n\n1. Stop all API writes immediately\n2. Wait **2 minutes minimum** (not 60s — secondary limits are stricter)\n3. Resume with `sleep 3` between each call\n4. If 403 persists after 2 min, wait another 2 min before retrying\n\nNever batch all replies in a tight loop — always space them out.\n\n## Parallel thread resolution\n\nWhen a PR has more than 10 unresolved threads, addressing one commit per thread is slow. Use this strategy instead:\n\n### Group by file, batch per commit\n\n1. Sort `ALL_THREADS` by `path` — threads in the same file can share a single commit.\n2. Fix all threads in one file → `git commit` → `git push` → reply to **all** those threads with the same SHA → resolve them all.\n3. Move to the next file group and repeat.\n\nThis reduces N commits to (number of files touched), which is usually 3–5 instead of 15–30.\n\n### Posting replies concurrently (for large batches)\n\nFor truly independent thread groups (different files, no shared logic), you can post replies in parallel using background subshells — but always space out API writes:\n\n```bash\n# Post replies to a batch of threads concurrently, 3s apart\n(\n  sleep 3\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID1}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\n(\n  sleep 6\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID2}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\nwait  # wait for all background replies before resolving\n```\n\nThen resolve sequentially (GraphQL mutations):\n```bash\nfor THREAD_ID in \"$THREAD1\" \"$THREAD2\" \"$THREAD3\"; do\n  gh api graphql -f query=\"mutation { resolveReviewThread(input: {threadId: \\\"${THREAD_ID}\\\"}) { thread { isResolved } } }\"\n  sleep 3\ndone\n```\n\n**Always sleep 3s between individual API writes** — GitHub's secondary rate limit (403) triggers on bursts of >20 writes. Increase to `sleep 5` when posting more than 20 replies in a batch.\n\n## Resolving threads via GraphQL\n\nUse `resolveReviewThread` **only after** the commit is pushed and the reply is posted:\n\n```bash\ngh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"THREAD_ID\"}) { thread { isResolved } } }'\n```\n\n**Never call this mutation before committing the fix.** The orchestrator will verify actual unresolved counts via GraphQL after you output `ORCHESTRATOR:DONE` — false resolutions will be caught and you will be re-briefed.\n\n> `resolveReviewThread` is GraphQL-only — no REST equivalent. If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the queue-and-retry flow.\n\n### Verify actual count before outputting ORCHESTRATOR:DONE\n\nBefore claiming \"0 unresolved threads\", always query GitHub directly — don't rely on your own bookkeeping. Paginate all pages — a single `first: 100` query misses threads beyond page 1:\n\n```bash\n# Step 1: get total thread count\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n    }\n  }\n}' | jq '.data.repository.pullRequest.reviewThreads.totalCount'\n\n# Step 2: paginate all pages, count truly unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes { isResolved }\n        }\n      }\n    }\n  }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved threads: $UNRESOLVED\"\n```\n\nOnly output `ORCHESTRATOR:DONE` after this loop reports 0.\n","frontmatter":{"name":"pr-address","description":"Address PR review comments and loop until CI green and all comments resolved. TRIGGER when user asks to address comments, fix PR feedback, respond to reviewers, or babysit/monitor a PR.","user-invocable":true,"argument-hint":"[PR number or URL] — if omitted, finds PR for current branch.","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":7822,"sizeBytes":31017},{"name":"SKILL.md","path":".claude/skills/pr-polish/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/pr-polish/SKILL.md","title":"pr-polish","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-polish\ndescription: Alternate /pr-review and /pr-address on a PR until the PR is truly mergeable — no new review findings, zero unresolved inline threads, zero unaddressed top-level reviews or issue comments, all CI checks green, and two consecutive quiet polls after CI settles. Use when the user wants a PR polished to merge-ready without setting a fixed number of rounds.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Polish\n\n**Goal.** Drive a PR to merge-ready by alternating `/pr-review` and `/pr-address` until **all** of the following hold:\n\n1. The most recent `/pr-review` produces **zero new findings** (no new inline comments, no new top-level reviews with a non-empty body).\n2. Every inline review thread reachable via GraphQL reports `isResolved: true`.\n3. Every non-bot, non-author top-level review has been acknowledged (replied-to) OR resolved via a thread it spawned.\n4. Every non-bot, non-author issue comment has been acknowledged (replied-to).\n5. Every CI check is `conclusion: \"success\"` or `\"skipped\"` / `\"neutral\"` — none `\"failure\"` or still pending.\n6. **Two consecutive post-CI polls** (≥60s apart) stay clean — no new threads, no new non-empty reviews, no new issue comments. Bots (coderabbitai, sentry, autogpt-pr-reviewer) frequently post late after CI settles; a single green snapshot is not sufficient.\n7. **No requested-but-unanswered review.** If a `/review` comment was posted on the PR and the review bot has not replied yet, quiet polls do **not** count toward condition 6 — keep polling until the review lands or the wait budget expires. See [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n**Do not stop at a fixed number of rounds.** If round N introduces new comments, round N+1 is required. Cap at `_MAX_ROUNDS = 10` as a safety valve, but expect 2–5 in practice.\n\n## TodoWrite\n\nBefore starting, write two todos so the user can see the loop progression:\n\n- `Round {current}: /pr-review + /pr-address on PR #{N}` — current iteration.\n- `Final polish polling: 2 consecutive clean polls, CI green, 0 unresolved` — runs after the last non-empty review round.\n\nUpdate the `current` round counter at the start of each iteration; mark `completed` only when the round's address step finishes (all new threads addressed + resolved).\n\n## Find the PR\n\n```bash\nARG_PR=\"${ARG:-}\"\n# Normalize URL → numeric ID if the skill arg is a pull-request URL.\nif [[ \"$ARG_PR\" =~ ^https?://github\\.com/[^/]+/[^/]+/pull/([0-9]+) ]]; then\n  ARG_PR=\"${BASH_REMATCH[1]}\"\nfi\nPR=\"${ARG_PR:-$(gh pr list --head \"$(git branch --show-current)\" --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')}\"\nif [ -z \"$PR\" ] || [ \"$PR\" = \"null\" ]; then\n  echo \"No PR found for current branch. Provide a PR number or URL as the skill arg.\"\n  exit 1\nfi\necho \"Polishing PR #$PR\"\n```\n\n## The outer loop\n\n```text\nround = 0\nwhile round < _MAX_ROUNDS:\n    round += 1\n    baseline = snapshot_state(PR)   # see \"Snapshotting state\" below\n    invoke_skill(\"pr-review\", PR)   # posts findings as inline comments / top-level review\n    findings = diff_state(PR, baseline)\n    if findings.total == 0:\n        break  # no new findings → go to polish polling\n    invoke_skill(\"pr-address\", PR)  # resolves every unresolved thread + CI failure\n# Post-loop: polish polling (see below).\npolish_polling(PR)\n```\n\n### Optional: the review bot\n\n`invoke_skill(\"pr-review\", PR)` is the agent's **own** review and always runs every round. In addition to it, the repo's review bot can be summoned on demand by commenting `/review` on the PR (see the `open-pr` skill):\n\n```bash\ngh pr comment \"${PR}\" --body \"/review\"\n```\n\nThis is an **option, not a required step**, and it is **not** a substitute for the `/pr-review` round — use it when you want a second opinion the outer loop cannot produce itself, or when the PR carries no bot findings at all yet. Bot findings land as inline threads and top-level reviews, exactly like the agent's own findings, so both feed the same `invoke_skill(\"pr-address\", PR)` step — no separate handling is needed. If you do request one, the exit conditions must wait for it; see [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n### Snapshotting state\n\nBefore each `/pr-review`, capture a baseline so the diff after the review reflects **only** what the review just added (not pre-existing threads):\n\n```bash\n# Inline threads — total count + latest databaseId per thread\ngh api graphql -f query=\"\n{\n  repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n    pullRequest(number: ${PR}) {\n      reviewThreads(first: 100) {\n        totalCount\n        nodes {\n          id\n          isResolved\n          comments(last: 1) { nodes { databaseId } }\n        }\n      }\n    }\n  }\n}\" > /tmp/baseline_threads.json\n\n# Top-level reviews — count + latest id per non-empty review\ngh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate \\\n  --jq '[.[] | select((.body // \"\") != \"\") | {id, user: .user.login, state, submitted_at}]' \\\n  > /tmp/baseline_reviews.json\n\n# Issue comments — count + latest id per non-bot, non-author comment.\n# Bots are filtered by User.type == \"Bot\" (GitHub sets this for app/bot\n# accounts like coderabbitai, github-actions, sentry-io). The author is\n# filtered by comparing login to the PR author — export it so jq can see it.\nAUTHOR=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}\" --jq '.user.login')\n# Slash-command triggers are excluded here too: a `/review` posted by a\n# maintainer is non-bot and non-author, so without this it enters the baseline\n# and later registers as a \"new finding\", forcing an address round over a\n# comment with nothing to address.\ngh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq --arg author \"$AUTHOR\" \\\n      '[.[][] | select(.user.type != \"Bot\" and .user.login != $author)\n             | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)\n             | {id, user: .user.login, created_at}]' \\\n  > /tmp/baseline_issue_comments.json\n```\n\n### Diffing after a review\n\nAfter `/pr-review` runs, any of these counting as \"new findings\" means another address round is needed:\n\n- New inline thread `id` not in the baseline.\n- An existing thread whose latest comment `databaseId` is higher than the baseline's (new reply on an old thread).\n- A new top-level review `id` with a non-empty body.\n- A new issue comment `id` from a non-bot, non-author user.\n\nIf any of the four buckets is non-empty → not done; invoke `/pr-address` and loop.\n\n## Polish polling\n\nOnce `/pr-review` produces zero new findings, do **not** exit yet. Bots (coderabbitai, sentry, autogpt-pr-reviewer) commonly post late reviews after CI settles — 30–90 seconds after the final push. Poll at 60-second intervals:\n\n```text\nNON_SUCCESS_TERMINAL = {\"failure\", \"cancelled\", \"timed_out\", \"action_required\", \"startup_failure\"}\nclean_polls = 0\nrequired_clean = 2\nwhile clean_polls < required_clean:\n    # 1. CI gate — any terminal non-success conclusion (not just \"failure\")\n    # must trigger /pr-address. \"success\", \"skipped\", \"neutral\" are clean;\n    # anything else (including cancelled, timed_out, action_required) is a\n    # blocker that won't self-resolve.\n    ci = fetch_check_runs(PR)\n    if any ci.conclusion in NON_SUCCESS_TERMINAL:\n        invoke_skill(\"pr-address\", PR)  # address failures + any new comments\n        baseline = snapshot_state(PR)   # reset — push during address invalidates old baseline\n        clean_polls = 0\n        continue\n    if any ci.conclusion is None (still in_progress):\n        sleep 60; continue  # wait without counting this as clean\n\n    # 2. Comment / thread gate\n    threads = fetch_unresolved_threads(PR)\n    new_issue_comments = diff_against_baseline(issue_comments)\n    new_reviews = diff_against_baseline(reviews)\n    if threads or new_issue_comments or new_reviews:\n        invoke_skill(\"pr-address\", PR)\n        baseline = snapshot_state(PR)   # reset — the address loop just dealt with these,\n                                        # otherwise they stay \"new\" relative to the old baseline forever\n        clean_polls = 0\n        continue\n\n    # 3. Mergeability gate\n    mergeable = gh api repos/.../pulls/${PR} --jq '.mergeable'\n    if mergeable == false (CONFLICTING):\n        resolve_conflicts(PR)  # see pr-address skill\n        clean_polls = 0\n        continue\n    if mergeable is null (UNKNOWN):\n        sleep 60; continue\n\n    # 4. Pending-review gate — a /review with no bot answer yet.\n    # Quiet is exactly what a bot still thinking looks like, so don't bank\n    # a clean poll on it. review_requested_but_unanswered() already returns\n    # false once REVIEW_WAIT_BUDGET is spent, so the loop cannot hang here.\n    if review_requested_but_unanswered(PR):\n        sleep 60; continue\n\n    clean_polls += 1\n    sleep 60\n```\n\nOnly after `clean_polls == 2` do you report `ORCHESTRATOR:DONE`.\n\n### Concrete CI fetch (don't parse `gh pr checks` text columns)\n\nThe `fetch_check_runs(PR)` step above must use `--json`, not the default text output. Job names can contain spaces and parentheses (e.g. `test (3.11)`, `Analyze (python)`), so `gh pr checks $PR | awk '{print $2}'` extracts `(3.11)` instead of the status — leading to a clean-poll firing while jobs are still pending.\n\n```bash\n# Reliable: use --json so columns are unambiguous.\nci_json=$(gh pr checks $PR --repo Significant-Gravitas/AutoGPT --json name,state,bucket)\npending=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"pending\")] | length')\nfailed=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"fail\" or .bucket == \"cancel\")] | length')\n\n# Buckets are: pass | fail | pending | cancel | skipping\n# (NOTE: gh pr checks does NOT expose `conclusion` as a JSON field —\n# only `bucket`. Don't confuse with the GitHub REST API's check_runs\n# endpoint, which DOES use conclusion.)\n```\n\nMap back to the pseudocode above: `bucket == \"pending\"` is `ci.conclusion is None (still in_progress)`; `bucket in {\"fail\", \"cancel\"}` is `ci.conclusion in NON_SUCCESS_TERMINAL`; `bucket in {\"pass\", \"skipping\"}` is clean.\n\n### Waiting on a requested bot review\n\n`autogpt-pr-reviewer[bot]` typically takes ~30 minutes to answer a `/review` — far longer than the 60-second poll interval, so without this gate two clean polls would report `ORCHESTRATOR:DONE` while the requested review is still in flight.\n\n`review_requested_but_unanswered(PR)` is:\n\nRecompute from the API on **every** poll — never track elapsed time in a loop\nvariable, which is how this gate becomes an infinite wait. `--paginate`\nconcatenates one array per page, so `last` picks the last item of the final\npage rather than the newest overall; use `--slurp` and `max_by`. An answer is\n*either* a top-level review or an inline review comment, whichever is later —\nan inline-only reply is still an answer, and counting only top-level reviews\nleft the gate waiting after its threads had already been addressed.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\n`REVIEW_WAIT_BUDGET = 60 minutes`, measured as `now - REQUESTED_AT`:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\nAn observed reply on this repo took **46 minutes**, so a 45-minute budget\nwould have abandoned a review that was about to arrive. 60 gives headroom.\n\nPast the budget, stop waiting, resume counting clean polls normally, and note\nin the final report that the requested bot review never arrived. The loop must\nnever hang on a bot.\n\n**Never post a second `/review` while one is unanswered.** Check\n`review_requested_but_unanswered(PR)` before triggering, not just before\nexiting — otherwise each round requests another review and the bot answers a\nqueue of duplicates.\n\n### Why 2 clean polls, not 1\n\nA single green snapshot can be misleading — the final CI check often completes ~30s before a bot posts its delayed review. One quiet cycle does not prove the PR is stable; two consecutive cycles with no new threads, reviews, or issue comments arriving gives high confidence nothing else is incoming.\n\n### Why checking every source each poll\n\n`/pr-address` polling inside a single round already re-checks its own comments, but `/pr-polish` sits a level above and must also catch:\n\n- New top-level reviews (autogpt-pr-reviewer sometimes posts structured feedback only after several CI green cycles).\n- Issue comments from human reviewers (not caught by inline thread polling).\n- Sentry bug predictions that land on new line numbers post-push.\n- Merge conflicts introduced by a race between your push and a merge to `dev`.\n\n## Invocation pattern\n\nDelegate to existing skills with the `Skill` tool; do not re-implement the review or address logic inline. This keeps the polish loop focused on orchestration and lets the child skills evolve independently.\n\n```python\nSkill(skill=\"pr-review\",  args=pr_url)\nSkill(skill=\"pr-address\", args=pr_url)\n```\n\nAfter each child invocation, re-query GitHub state directly — never trust a summary for the stop condition. The orchestrator's `ORCHESTRATOR:DONE` is verified against actual GraphQL / REST responses per the rules in `pr-address`'s \"Verify actual count before outputting ORCHESTRATOR:DONE\" section.\n\n### **Auto-continue: do NOT end your response between child skills**\n\n`/pr-polish` is a single orchestration task — one invocation drives the PR all the way to merge-ready. When a child `Skill()` call returns control to you:\n\n- Do NOT summarize and stop.\n- Do NOT wait for user confirmation to continue.\n- Immediately, in the same response, perform the next loop step: state diff → decide next action → next `Skill()` call or polling sleep.\n\nThe child skill returning is a **loop iteration boundary**, not a conversation turn boundary. You are expected to keep going until one of the exit conditions in the opening section is met (2 consecutive clean polls, `_MAX_ROUNDS` hit, or an unrecoverable error).\n\nIf the user needs to approve a risky action mid-loop (e.g., a force-push or a destructive git operation), pause there — but not at the routine \"round N finished, round N+1 needed\" boundary. Those are silent transitions.\n\n### **Run /pr-polish in the foreground — never in a background agent**\n\nSpawning `/pr-polish` inside an `Agent(subagent_type=\"general-purpose\")` background task **does not work**. Background agents don't inherit the parent's slash-command registry, so `Skill(skill=\"pr-review\")` and `Skill(skill=\"pr-address\")` calls aren't available — the agent has to manually replicate the child skills' logic, which is fragile and tends to stall on the first network or rate-limit hiccup. Symptom: the background task reports `stalled: no progress for 600s` mid-review.\n\nRun `/pr-polish` inline in the foreground conversation. If the user asks for \"/pr-polish + /pr-test in parallel\", split them: foreground `/pr-polish`, and ONLY then can the test step go to a background agent (because `/pr-test` doesn't itself need to invoke skills).\n\n### **You MUST invoke `Skill(pr-review)` every round — even when bot reviews already exist**\n\nA common failure mode: CodeRabbit / autogpt-pr-reviewer / Sentry have already posted findings on the PR, and the orchestrator skips the `Skill(pr-review)` step on the assumption that \"review has been done.\" That's wrong — the outer loop's purpose is to layer **the agent's own review** on top of the bot reviews, catching issues the bots miss (architecture, naming, cross-file invariants, hidden coupling). If the orchestrator only addresses bot findings without ever running its own review, the loop converges to \"bot-clean\" but not \"agent-reviewed-clean,\" and the user reasonably asks \"did /pr-polish even read the diff?\"\n\n**Self-check before reporting `ORCHESTRATOR:DONE`:** confirm at least one `Skill(skill=\"pr-review\")` call appears in the current orchestration. If none, the loop is incomplete — go back and run one round.\n\n## GitHub rate limits\n\nThis skill issues many GraphQL calls (one review-thread query per outer iteration plus per-poll queries inside polish polling). Expect the GraphQL budget to be tight on large PRs. When `gh api rate_limit --jq .resources.graphql.remaining` drops below ~200, back off:\n\n- Fall back to REST for reads (flat `/pulls/{N}/comments`, `/pulls/{N}/reviews`, `/issues/{N}/comments`) per the `pr-address` skill's GraphQL-fallback section.\n- Queue thread resolutions (GraphQL-only) until the budget resets; keep making progress on fixes + REST replies meanwhile.\n- `sleep 5` between any batch of ≥20 writes to avoid secondary rate limits.\n\n## Safety valves\n\n- `_MAX_ROUNDS = 10` — if review+address rounds exceed this, stop and escalate to the user with a summary of what's still unresolved. A PR that cannot converge in 10 rounds has systemic issues that need human judgment.\n- After each commit, run `poetry run format` / `pnpm format && pnpm lint && pnpm types` per the target codebase's conventions. A failing format check is CI `failure` that will never self-resolve.\n- Every `/pr-review` round checks for **duplicate** concerns first (via `pr-review`'s own \"Fetch existing review comments\" step) so the loop does not re-post the same finding that a prior round already resolved.\n\n## Reporting\n\nWhen the skill finishes (either via two clean polls or hitting `_MAX_ROUNDS`), produce a compact summary:\n\n```\nPR #{N} polish complete ({rounds_completed} rounds):\n- {X} inline threads opened and resolved\n- {Y} CI failures fixed\n- {Z} new commits pushed\nFinal state: CI green, {total} threads all resolved, mergeable.\n```\n\nIf exiting via `_MAX_ROUNDS`, flag explicitly:\n\n```\nPR #{N} polish stopped at {_MAX_ROUNDS} rounds — NOT merge-ready:\n- {N} threads still unresolved: {titles}\n- CI status: {summary}\nNeeds human review.\n```\n\n## When to use this skill\n\nUse when the user says any of:\n- \"polish this PR\"\n- \"keep reviewing and addressing until it's mergeable\"\n- \"loop /pr-review + /pr-address until done\"\n- \"make sure the PR is actually merge-ready\"\n\nDo **not** use when:\n- User wants just one review pass (→ `/pr-review`).\n- User wants to address already-posted comments without further self-review (→ `/pr-address`).\n- A fixed round count is explicitly requested (e.g., \"do 3 rounds\") — honour the count instead of converging.\n","frontmatter":{"name":"pr-polish","description":"Alternate /pr-review and /pr-address on a PR until the PR is truly mergeable — no new review findings, zero unresolved inline threads, zero unaddressed top-level reviews or issue comments, all CI checks green, and two consecutive quiet polls after CI settles. Use when the user wants a PR polished to merge-ready without setting a fixed number of rounds.","user-invocable":true,"argument-hint":"[PR number or URL] — if omitted, finds PR for current branch.","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":5018,"sizeBytes":20052},{"name":"SKILL.md","path":".claude/skills/pr-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/pr-review/SKILL.md","title":"pr-review","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-review\ndescription: Review a PR for correctness, security, code quality, and testing issues. TRIGGER when user asks to review a PR, check PR quality, or give feedback on a PR.\nuser-invocable: true\nargs: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Review\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nBefore reading code, understand the **why**, **what**, and **how** from the PR description:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\nEvery PR should have a Why / What / How structure. If any of these are missing, note it as feedback.\n\n## Read the diff\n\n```bash\ngh pr diff {N}\n```\n\n## Fetch existing review comments\n\nBefore posting anything, fetch existing inline comments to avoid duplicates:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\n```\n\n## What to check\n\n**Description quality:** Does the PR description cover Why (motivation/problem), What (summary of changes), and How (approach/implementation details)? If any are missing, request them — you can't judge the approach without understanding the problem and intent.\n\n**Correctness:** logic errors, off-by-one, missing edge cases, race conditions (TOCTOU in file access, credit charging), error handling gaps, async correctness (missing `await`, unclosed resources).\n\n**Security:** input validation at boundaries, no injection (command, XSS, SQL), secrets not logged, file paths sanitized (`os.path.basename()` in error messages).\n\n**Code quality:** apply rules from backend/frontend CLAUDE.md files and root `AGENTS.md`, including its \"User-facing copy\" section (text a person reads says workflow, never agent or graph).\n\n**Architecture:** DRY, single responsibility, modular functions. `Security()` vs `Depends()` for FastAPI auth. `data:` for SSE events, `: comment` for heartbeats. `transaction=True` for Redis pipelines.\n\n**Testing:** edge cases covered, colocated `*_test.py` (backend) / `__tests__/` (frontend), mocks target where symbol is **used** not defined, `AsyncMock` for async.\n\n## Output format\n\nEvery comment **must** be prefixed with `🤖` and a criticality badge:\n\n| Tier | Badge | Meaning |\n|---|---|---|\n| Blocker | `🔴 **Blocker**` | Must fix before merge |\n| Should Fix | `🟠 **Should Fix**` | Important improvement |\n| Nice to Have | `🟡 **Nice to Have**` | Minor suggestion |\n| Nit | `🔵 **Nit**` | Style / wording |\n\nExample: `🤖 🔴 **Blocker**: Missing error handling for X — suggest wrapping in try/except.`\n\n## Post inline comments\n\nFor each finding, post an inline comment on the PR (do not just write a local report):\n\n```bash\n# Get the latest commit SHA for the PR\nCOMMIT_SHA=$(gh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.head.sha')\n\n# Post an inline comment on a specific file/line\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments \\\n  -f body=\"🤖 🔴 **Blocker**: <description>\" \\\n  -f commit_id=\"$COMMIT_SHA\" \\\n  -f path=\"<file path>\" \\\n  -F line=<line number>\n```\n","frontmatter":{"name":"pr-review","description":"Review a PR for correctness, security, code quality, and testing issues. TRIGGER when user asks to review a PR, check PR quality, or give feedback on a PR.","user-invocable":true,"args":"[PR number or URL] — if omitted, finds PR for current branch.","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":836,"sizeBytes":3217},{"name":"SKILL.md","path":".claude/skills/pr-test/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/pr-test/SKILL.md","title":"pr-test","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-test\ndescription: \"E2E manual testing of PRs/branches using docker compose, agent-browser, and API calls. TRIGGER when user asks to manually test a PR, test a feature end-to-end, or run integration tests against a running system.\"\nuser-invocable: true\nargument-hint: \"[worktree path or PR number] — tests the PR in the given worktree. Optional flags: --fix (auto-fix issues found)\"\nmetadata:\n  author: autogpt-team\n  version: \"2.3.0\"\n---\n\n# Manual E2E Test\n\nTest a PR/branch end-to-end by building the full platform, interacting via browser and API, capturing screenshots, and reporting results.\n\n**Changelog 2.2.0** — auth flow updated for Better Auth (Supabase signup is\ngone), env-setup gaps closed, a proven Playwright fallback for agent-browser,\na billing-test trap that produces false passes, safer process cleanup, and a\nmock-provider pattern for deterministic $0 testing. Learned on\n[#14206](https://github.com/Significant-Gravitas/AutoGPT/pull/14206) — see the\n[evidence comment](https://github.com/Significant-Gravitas/AutoGPT/pull/14206#issuecomment-5511429524).\n**2.2.1** — corrects two claims from 2.2.0 that didn't survive live-stack\nverification (JWKS does **not** rotate on a frontend restart; the local\nPostgres port is `5432`, not `54322`) and hardens the auth setup (explicit\npassword-length/allowlist/rate-limit failure modes, fail-fast on an empty\ntoken, password kept out of process args).\n**2.3.0** — screenshots are posted as GitHub comment attachments\n(`gh pr comment --attach`, needs gh >= 2.99.0) instead of being pushed to a\n`test-screenshots/*` branch, which accumulated one branch per tested PR and\nmade every old report's images depend on that branch surviving.\n\n## Critical Requirements\n\nThese are NON-NEGOTIABLE. Every test run MUST satisfy ALL the following:\n\n### 1. Screenshots at Every Step\n- Take a screenshot at EVERY significant test step — not just at the end\n- Every test scenario MUST have at least one BEFORE and one AFTER screenshot\n- Name screenshots sequentially: `{NN}-{action}-{state}.png` (e.g., `01-credits-before.png`, `02-credits-after.png`)\n- If a screenshot is missing for a scenario, the test is INCOMPLETE — go back and take it\n\n### 2. Screenshots MUST Be Posted to PR\n- Attach ALL screenshots to the PR comment with `gh pr comment --attach` (see Step 7). **Never push image files to a repo branch** — attachments live in GitHub's own asset store and leave nothing behind in the repo\n- Post a PR comment with ALL screenshots embedded inline, each with its explanation\n- This is NOT optional — every test run MUST end with a PR comment containing screenshots\n- If the upload fails, retry. If it still fails, list failed files and require manual drag-and-drop/paste attachment in the PR comment, and mark the run INCOMPLETE\n\n### 3. State Verification with Before/After Evidence\n- For EVERY state-changing operation (API call, user action), capture the state BEFORE and AFTER\n- Log the actual API response values (e.g., `credits_before=100, credits_after=95`)\n- Screenshot MUST show the relevant UI state change\n- Compare expected vs actual values explicitly — do not just eyeball it\n\n### 4. Negative Test Cases Are Mandatory\n- Test at least ONE negative case per feature (e.g., insufficient credits, invalid input, unauthorized access)\n- Verify error messages are user-friendly and accurate\n- Verify the system state did NOT change after a rejected operation\n\n### 5. Test Report Must Include Full Evidence\nEach test scenario in the report MUST have:\n- **Steps**: What was done (exact commands or UI actions)\n- **Expected**: What should happen\n- **Actual**: What actually happened\n- **API Evidence**: Before/after API response values for state-changing operations\n- **Screenshot Evidence**: Before/after screenshots with explanations\n\n## State Manipulation for Realistic Testing\n\n**Billing-test trap — this one produces a false pass, not a visible failure.**\nLLM block cost filters key on the *platform-owned* credential id. A run made\nwith the test user's own API key bills nothing by design, so a credits\nbefore/after assertion silently passes on zero deltas either way. Any test\nthat verifies credit reconciliation MUST use the system credential, not a\nuser-supplied key. Related: Ollama block entries are configured with an\nexplicit `$0` run-based cost (`BlockCostType.RUN, cost_amount=0` in\n`block_cost_config.py`), not a token-metered one — so pre/post-flight cost\ndeltas are always 0 for them regardless of credential. Never use an Ollama\nmodel to test credit reconciliation; pick any hosted model billed through the\nsystem credential instead.\n\nWhen testing features that depend on specific states (rate limits, credits, quotas):\n\n1. **Use Redis CLI to set counters directly:**\n   ```bash\n   # Find the Redis container\n   REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep redis | head -1)\n   # Set a key with expiry\n   docker exec $REDIS_CONTAINER redis-cli SET key value EX ttl\n   # Example: Set rate limit counter to near-limit\n   docker exec $REDIS_CONTAINER redis-cli SET \"rate_limit:user:$PR_TEST_USER_EMAIL\" 99 EX 3600\n   # Example: Check current value\n   docker exec $REDIS_CONTAINER redis-cli GET \"rate_limit:user:$PR_TEST_USER_EMAIL\"\n   ```\n\n2. **Use API calls to check before/after state:**\n   ```bash\n   # BEFORE: Record current state\n   BEFORE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits BEFORE: $BEFORE\"\n\n   # Perform the action...\n\n   # AFTER: Record new state and compare\n   AFTER=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits AFTER: $AFTER\"\n   echo \"Delta: $(( BEFORE - AFTER ))\"\n   ```\n\n3. **Take screenshots BEFORE and AFTER state changes** — the UI must reflect the backend state change\n\n4. **Never rely on mocked/injected browser state** — always use real backend state. Do NOT use `agent-browser eval` to fake UI state. The backend must be the source of truth.\n\n5. **Use direct DB queries when needed:**\n   ```bash\n   # Query via Supabase's PostgREST or docker exec into the DB\n   docker exec supabase-db psql -U supabase_admin -d postgres -c \"SELECT credits FROM user_credits WHERE user_id = '...';\"\n   ```\n\n6. **After every API test, verify the state change actually persisted:**\n   ```bash\n   # Example: After a credits purchase, verify DB matches API\n   API_CREDITS=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   DB_CREDITS=$(docker exec supabase-db psql -U supabase_admin -d postgres -t -c \"SELECT credits FROM user_credits WHERE user_id = '...';\" | tr -d ' ')\n   [ \"$API_CREDITS\" = \"$DB_CREDITS\" ] && echo \"CONSISTENT\" || echo \"MISMATCH: API=$API_CREDITS DB=$DB_CREDITS\"\n   ```\n\n## Arguments\n\n- `$ARGUMENTS` — worktree path (e.g. `$REPO_ROOT`) or PR number\n- If `--fix` flag is present, auto-fix bugs found and push fixes (like pr-address loop)\n\n## Step 0: Resolve the target\n\n```bash\n# If argument is a PR number, find its worktree\ngh pr view {N} --json headRefName --jq '.headRefName'\n# If argument is a path, use it directly\n```\n\nDetermine:\n- `REPO_ROOT` — the root repo directory: `git -C \"$WORKTREE_PATH\" worktree list | head -1 | awk '{print $1}'` (or `git rev-parse --show-toplevel` if not a worktree)\n- `WORKTREE_PATH` — the worktree directory\n- `PLATFORM_DIR` — `$WORKTREE_PATH/autogpt_platform`\n- `BACKEND_DIR` — `$PLATFORM_DIR/backend`\n- `FRONTEND_DIR` — `$PLATFORM_DIR/frontend`\n- `PR_NUMBER` — the PR number (from `gh pr list --head $(git branch --show-current)`)\n- `PR_TITLE` — the PR title, slugified (e.g. \"Add copilot permissions\" → \"add-copilot-permissions\")\n- `RESULTS_DIR` — `$REPO_ROOT/test-results/PR-{PR_NUMBER}-{slugified-title}`\n\nCreate the results directory:\n```bash\nPR_NUMBER=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')\nPR_TITLE=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json title --jq '.[0].title' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | head -c 50)\nRESULTS_DIR=\"$REPO_ROOT/test-results/PR-${PR_NUMBER}-${PR_TITLE}\"\nmkdir -p $RESULTS_DIR\n```\n\n**Test user credentials** — required to log into the UI or call authenticated APIs.\n\nNEVER hardcode these in this SKILL, a PR comment, a screenshot, or any committed file. Sources, in priority order:\n\n1. **Env vars** (CI / preconfigured local shell): `$PR_TEST_USER_EMAIL` + `$PR_TEST_USER_PASSWORD`. If both are set, use them.\n2. **Interactive prompt** (everything else, including dev-preview runs): if the env vars are not set, ASK the user at the start of the run — e.g. \"I need test-user credentials for this run; paste the email and password.\" Hold them in shell vars only for the duration of the run. Do not echo, log, or write them to disk.\n\nAcquire the variables — env first, prompt if missing — and only then lock them in:\n\n```bash\n# 1. Prefer env vars (CI / preconfigured shell). Prompt only for the\n#    specific var that is unset so an already-exported credential is\n#    not overwritten by the prompt when only the other one is missing.\nif [ -z \"${PR_TEST_USER_EMAIL:-}\" ] || [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n  echo \"Test user credentials required for this run.\"\n  if [ -z \"${PR_TEST_USER_EMAIL:-}\" ]; then\n    read -r -p   \"Email:    \" PR_TEST_USER_EMAIL\n  fi\n  if [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n    read -r -s -p \"Password: \" PR_TEST_USER_PASSWORD\n    echo\n  fi\n  export PR_TEST_USER_EMAIL PR_TEST_USER_PASSWORD\nfi\n\n# 2. Lock them in — fail loudly if either is STILL unset (e.g. the user\n#    pressed Enter on an empty prompt). The error message names the var so\n#    the agent / operator knows what to fix.\n: \"${PR_TEST_USER_EMAIL:?PR_TEST_USER_EMAIL is empty after env+prompt — supply a value before re-running}\"\n: \"${PR_TEST_USER_PASSWORD:?PR_TEST_USER_PASSWORD is empty after env+prompt — supply a value before re-running}\"\n```\n\nFor **local docker-compose** runs, a fresh dev user is created on first call to the signup snippet below. For **dev-preview** runs, the test user lives in the project's hosted auth backend — ask the user for the current valid credentials each session (the previously-shared `test@test.com` test account was disabled on 2026-05-23 after its credentials leaked into this very SKILL — do NOT re-introduce a default). **`PR_TEST_USER_PASSWORD` should always be a throwaway/test-only credential, never a real account's password** — the auth requests in 3h go over plain HTTP on `localhost:3000` for local runs, which has no transport encryption. If a dev-preview run's target isn't on `localhost`, confirm it's HTTPS before sending credentials to it.\n\n## Step 1: Understand the PR\n\nBefore testing, understand what changed:\n\n```bash\ncd $WORKTREE_PATH\n\n# Read PR description to understand the WHY\ngh pr view {N} --json body --jq '.body'\n\ngit log --oneline dev..HEAD | head -20\ngit diff dev --stat\n```\n\nRead the PR description (Why / What / How) and changed files to understand:\n0. **Why** does this PR exist? What problem does it solve?\n1. **What** feature/fix does this PR implement?\n2. **How** does it work? What's the approach?\n3. What components are affected? (backend, frontend, copilot, executor, etc.)\n4. What are the key user-facing behaviors to test?\n\n## Step 2: Write test scenarios\n\nBased on the PR analysis, write a test plan to `$RESULTS_DIR/test-plan.md`:\n\n```markdown\n# Test Plan: PR #{N} — {title}\n\n## Scenarios\n1. [Scenario name] — [what to verify]\n2. ...\n\n## API Tests (if applicable)\n1. [Endpoint] — [expected behavior]\n   - Before state: [what to check before]\n   - After state: [what to verify changed]\n\n## UI Tests (if applicable)\n1. [Page/component] — [interaction to test]\n   - Screenshot before: [what to capture]\n   - Screenshot after: [what to capture]\n\n## Negative Tests (REQUIRED — at least one per feature)\n1. [What should NOT happen] — [how to trigger it]\n   - Expected error: [what error message/code]\n   - State unchanged: [what to verify did NOT change]\n```\n\n**Be critical** — include edge cases, error paths, and security checks. Every scenario MUST specify what screenshots to take and what state to verify.\n\n## Step 3.0: Claim the testing lock (coordinate parallel agents)\n\nMultiple worktrees share the same host — Docker infra (postgres, redis, clamav), app ports (3000/8006/…), and the test user. Two agents running `/pr-test` concurrently will corrupt each other's state (connection-pool exhaustion, port binds failing silently, cross-test assertions). Use the root-worktree lock file to take turns.\n\n### Lock file contract\n\nPath (**always** the root worktree so all siblings see it): `$REPO_ROOT/.ign.testing.lock`\n\nBody (one `key=value` per line):\n```\nholder=<pr-XXXXX-purpose>\npid=<pid-or-\"self\">\nstarted=<iso8601>\nheartbeat=<iso8601, updated every ~2 min>\nworktree=<full path>\nbranch=<branch name>\nintent=<one-line description + rough duration>\n```\n\n### Claim\n\n```bash\nLOCK=$REPO_ROOT/.ign.testing.lock\nNOW=$(date -u +%Y-%m-%dT%H:%MZ)\nSTALE_AFTER_MIN=5\n\nif [ -f \"$LOCK\" ]; then\n  HB=$(grep '^heartbeat=' \"$LOCK\" | cut -d= -f2)\n  HB_EPOCH=$(date -j -f '%Y-%m-%dT%H:%MZ' \"$HB\" +%s 2>/dev/null || date -d \"$HB\" +%s 2>/dev/null || echo 0)\n  AGE_MIN=$(( ( $(date -u +%s) - HB_EPOCH ) / 60 ))\n  if [ \"$AGE_MIN\" -gt \"$STALE_AFTER_MIN\" ]; then\n    echo \"WARN: stale lock (${AGE_MIN}m old) — reclaiming\"\n    cat \"$LOCK\" | sed 's/^/  stale: /'\n  else\n    echo \"Another agent holds the lock:\"; cat \"$LOCK\"\n    echo \"Wait until released or resume after $((STALE_AFTER_MIN - AGE_MIN))m.\"\n    exit 1\n  fi\nfi\n\ncat > \"$LOCK\" <<EOF\nholder=pr-${PR_NUMBER}-e2e\npid=self\nstarted=$NOW\nheartbeat=$NOW\nworktree=$WORKTREE_PATH\nbranch=$(cd $WORKTREE_PATH && git branch --show-current)\nintent=E2E test PR #${PR_NUMBER}, native mode, ~60min\nEOF\necho \"Lock claimed\"\n```\n\n### Heartbeat (MUST run in background during the whole test)\n\nWithout a heartbeat a crashed agent keeps the lock forever. Run this as a background process right after claim:\n\n```bash\n(while true; do\n   sleep 120\n   [ -f \"$LOCK\" ] || exit 0   # lock released → exit heartbeat\n   perl -i -pe \"s/^heartbeat=.*/heartbeat=$(date -u +%Y-%m-%dT%H:%MZ)/\" \"$LOCK\"\n done) &\nHEARTBEAT_PID=$!\necho \"$HEARTBEAT_PID\" > /tmp/pr-test-heartbeat.pid\n```\n\n### Release (always — even on failure)\n\n```bash\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\nUse a `trap` so release runs even on `exit 1`:\n```bash\ntrap 'kill \"$HEARTBEAT_PID\" 2>/dev/null; rm -f \"$LOCK\"' EXIT INT TERM\n```\n\n### **Release the lock AS SOON AS the test run is done**\n\nThe lock guards **test execution**, not **app lifecycle**. Once Step 5 (record results) and Step 6 (post PR comment) are complete, release the lock IMMEDIATELY — even if:\n\n- The native `poetry run app` / `pnpm dev` processes are still running so the user can keep poking at the app manually.\n- You're leaving docker containers up.\n- You're tailing logs for a minute or two.\n\nKeeping the lock held past the test run is the single most common way `/pr-test` stalls other agents. **The app staying up is orthogonal to the lock; don't conflate them.** Sibling worktrees running their own `/pr-test` will kill the stray processes and free the ports themselves (Step 3c/3e-native handle that) — they just need the lock file gone.\n\nConcretely, the sequence at the end of every `/pr-test` run (success or failure) is:\n\n```bash\n# 1. Write the final report + post PR comment — done above in Step 5/6.\n# 2. Release the lock right now, even if the app is still up.\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock (app may still be running)\" \\\n    >> $REPO_ROOT/.ign.testing.log\n# 3. Optionally leave the app running and note it so the user knows:\necho \"Native stack still running on :3000 / :8006 for manual poking. Kill with:\"\necho \"  pkill -9 -f 'poetry run app'; pkill -9 -f 'next-server|next dev'\"\n```\n\nIf a sibling agent's `/pr-test` needs to take over, it'll do the kill+rebuild dance from Step 3c/3e-native on its own — your only job is to not hold the lock file past the end of your test.\n\n### Shared status log\n\n`$REPO_ROOT/.ign.testing.log` is an append-only channel any agent can read/write. Use it for \"I'm waiting\", \"I'm done, resources free\", or post-run notes:\n```bash\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] <message>\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\n## Step 3: Environment setup\n\n### 3a. Copy .env files from the root worktree\n\nThe root worktree (`$REPO_ROOT`) has the canonical `.env` files with all API keys. Copy them to the target worktree:\n\n```bash\n# CRITICAL: .env files are NOT checked into git. They must be copied manually.\ncp $REPO_ROOT/autogpt_platform/.env $PLATFORM_DIR/.env\ncp $REPO_ROOT/autogpt_platform/backend/.env $BACKEND_DIR/.env\ncp $REPO_ROOT/autogpt_platform/frontend/.env $FRONTEND_DIR/.env\n```\n\n**A copy from the root worktree is no longer sufficient on a recent `dev`** —\nauth moved from Supabase to Better Auth (see 3h) and two vars are easy to\nmiss because nothing fails loudly without them, it just 401s later:\n\n- `$BACKEND_DIR/.env` needs `JWT_JWKS_URL` — the Better Auth JWKS endpoint the\n  backend verifies tokens against. The `localhost:3000` value below is for\n  **native mode only**. In docker mode it's harmless to have it in `.env`\n  because `docker-compose.platform.yml` overrides it with the\n  Compose-reachable `http://frontend:3000/api/auth/jwks` — but if you ever run\n  the backend against this `.env` value directly (bypassing Compose), a\n  `localhost` value inside a container resolves to itself, not the frontend,\n  and every call 401s with no other symptom.\n- `$FRONTEND_DIR/.env` needs its **own** `DATABASE_URL` — Better Auth runs\n  inside the Next.js app and talks to Postgres directly, it does not go\n  through the backend. **Derive it from `$BACKEND_DIR/.env`'s `DB_USER` /\n  `DB_PASS` / `DB_PORT` / `DB_NAME` rather than copying\n  `frontend/.env.default`'s placeholder verbatim** — the placeholder happens\n  to match the stock local defaults, but if `backend/.env`'s `DB_PASS` was\n  ever customized (rotated secret, non-default port), copying the placeholder\n  silently points Better Auth at the wrong database instead of the one the\n  rest of the stack actually uses.\n\n```bash\n# [ -n ... ], not grep -q alone — a present-but-empty JWT_JWKS_URL= would\n# otherwise be treated as \"already set\" and skip the fallback, leaving the\n# backend without a JWKS endpoint to verify tokens against.\n[ -n \"$(grep '^JWT_JWKS_URL=' $BACKEND_DIR/.env | cut -d= -f2-)\" ] || echo \"JWT_JWKS_URL=http://localhost:3000/api/auth/jwks\" >> $BACKEND_DIR/.env  # native mode only — see note above\n\nif [ -n \"$(grep '^DATABASE_URL=' $FRONTEND_DIR/.env | cut -d= -f2-)\" ]; then\n  # grep -q alone matches a present-but-empty DATABASE_URL= too, which would\n  # otherwise skip derivation and leave Better Auth pointed at nothing.\n  echo \"Frontend DATABASE_URL: already set (not touching it)\"\nelse\n  # cut -f2 (not -f2-) truncates any value containing '=' (base64 secrets do); -f2- keeps the rest.\n  DB_USER=$(grep '^DB_USER=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PASS=$(grep '^DB_PASS=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PORT=$(grep '^DB_PORT=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_NAME=$(grep '^DB_NAME=' $BACKEND_DIR/.env | cut -d= -f2-)\n  : \"${DB_USER:?}\" \"${DB_PASS:?}\" \"${DB_PORT:?}\" \"${DB_NAME:?}\"  # fail loudly, not with a silently-empty URL\n  # Percent-encode user/pass — a raw '@', '#', '?', '%', or ':' in either would\n  # otherwise be misparsed as URL structure instead of credential content.\n  # Via env vars, not `jq --arg`, which would put DB_PASS in the process arglist.\n  DB_USER_ENC=$(DB_USER_VAL=\"$DB_USER\" jq -rn '$ENV.DB_USER_VAL|@uri')\n  DB_PASS_ENC=$(DB_PASS_VAL=\"$DB_PASS\" jq -rn '$ENV.DB_PASS_VAL|@uri')\n  echo \"DATABASE_URL=postgresql://${DB_USER_ENC}:${DB_PASS_ENC}@localhost:${DB_PORT}/${DB_NAME}\" >> $FRONTEND_DIR/.env\n  # Reconstructed, not regex-redacted — a password containing '@' would otherwise\n  # leak its tail past a naive \"redact up to the first @\" pattern.\n  echo \"Frontend DATABASE_URL: postgresql://${DB_USER_ENC}:***@localhost:${DB_PORT}/${DB_NAME}\"\nfi\n```\n\n### 3b. Configure copilot authentication\n\nThe copilot needs an LLM API to function. Two approaches (try subscription first):\n\n#### Option 1: Subscription mode (preferred — uses your Claude Max/Pro subscription)\n\nThe `claude_agent_sdk` Python package **bundles its own Claude CLI binary** — no need to install `@anthropic-ai/claude-code` via npm. The backend auto-provisions credentials from environment variables on startup.\n\nRun the helper script to extract tokens from your host and auto-update `backend/.env` (works on macOS, Linux, and Windows/WSL):\n\n```bash\n# Extracts OAuth tokens and writes CLAUDE_CODE_OAUTH_TOKEN + CLAUDE_CODE_REFRESH_TOKEN into .env\nbash $BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env\n```\n\n**How it works:** The script reads the OAuth token from:\n- **macOS**: system keychain (`\"Claude Code-credentials\"`)\n- **Linux/WSL**: `~/.claude/.credentials.json`\n- **Windows**: `%APPDATA%/claude/.credentials.json`\n\nIt sets `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REFRESH_TOKEN`, and `CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true` in the `.env` file. On container startup, the backend auto-provisions `~/.claude/.credentials.json` inside the container from these env vars. The SDK's bundled CLI then authenticates using that file. No `claude login`, no npm install needed.\n\n**Note:** The OAuth token expires (~24h). If copilot returns auth errors, re-run the script and restart: `$BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env && docker compose up -d copilot_executor`\n\n#### Option 2: OpenRouter API key mode (fallback)\n\nIf subscription mode doesn't work, switch to API key mode using OpenRouter:\n\n```bash\n# In $BACKEND_DIR/.env, ensure these are set:\nCHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false\nCHAT_API_KEY=<value of OPEN_ROUTER_API_KEY from the same .env>\nCHAT_BASE_URL=https://openrouter.ai/api/v1\nCHAT_USE_CLAUDE_AGENT_SDK=true\n```\n\nUse `sed` to update these values:\n```bash\nORKEY=$(grep \"^OPEN_ROUTER_API_KEY=\" $BACKEND_DIR/.env | cut -d= -f2)\n[ -n \"$ORKEY\" ] || { echo \"ERROR: OPEN_ROUTER_API_KEY is missing in $BACKEND_DIR/.env\"; exit 1; }\nperl -i -pe 's/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false/' $BACKEND_DIR/.env\n# Add or update CHAT_API_KEY and CHAT_BASE_URL\ngrep -q \"^CHAT_API_KEY=\" $BACKEND_DIR/.env && perl -i -pe \"s|^CHAT_API_KEY=.*|CHAT_API_KEY=$ORKEY|\" $BACKEND_DIR/.env || echo \"CHAT_API_KEY=$ORKEY\" >> $BACKEND_DIR/.env\ngrep -q \"^CHAT_BASE_URL=\" $BACKEND_DIR/.env && perl -i -pe 's|^CHAT_BASE_URL=.*|CHAT_BASE_URL=https://openrouter.ai/api/v1|' $BACKEND_DIR/.env || echo \"CHAT_BASE_URL=https://openrouter.ai/api/v1\" >> $BACKEND_DIR/.env\n```\n\n### 3c. Stop conflicting containers\n\n```bash\n# Stop any running app containers (keep infra: supabase, redis, rabbitmq, clamav)\ndocker ps --format \"{{.Names}}\" | grep -E \"rest_server|executor|copilot|websocket|database_manager|scheduler|notification|frontend|migrate\" | while read name; do\n  docker stop \"$name\" 2>/dev/null\ndone\n```\n\n**Native mode also:** when running the app natively (see 3e-native), kill any stray host processes and free the app ports before starting — otherwise `poetry run app` and `pnpm dev` will fail to bind.\n\n**Kill by port, not by broad process pattern.** A pattern-based\n`pkill -f \"python.*backend\"` (or anything matching by worktree cwd) is too\ncoarse on a host running several worktrees — it has taken out the frontend\nand a mock server sitting on other ports along with the intended backend\nprocess. Target the pid actually holding each port instead — this only kills\nwhoever is bound to that specific port, which is narrower than a pattern\nmatch, but **it is not worktree isolation**: if a sibling worktree's own dev\nserver happens to be using one of these ports (e.g. its frontend also on\n:3000), this kills that too. `lsof` tells you who holds the port, not who\nowns it — a `docker-proxy` pid there means a compose stack owns it.\n\n```bash\n# Free app ports one at a time — errors per port are ignored (port may simply\n# be unused). `xargs -r` is GNU-only (macOS xargs rejects -r); the `[ -n ]`\n# guard below is the portable equivalent.\nfor port in 3000 8006 8001 8002 8005 8008; do\n  pids=$(lsof -ti :$port -sTCP:LISTEN 2>/dev/null)\n  [ -n \"$pids\" ] && kill -9 $pids 2>/dev/null || true\ndone\n```\n\n### 3e-native. Run the app natively (PREFERRED for iterative dev)\n\nNative mode runs infra (postgres, supabase, redis, rabbitmq, clamav) in docker but runs the backend and frontend directly on the host. This avoids the 3-8 minute `docker compose build` cycle on every backend change — code edits are picked up on process restart (seconds) instead of a full image rebuild.\n\n**When to prefer native mode (default for this skill):**\n- Iterative dev/debug loops where you're editing backend or frontend code between test runs\n- Any PR that touches Python/TS source but not Dockerfiles, compose config, or infra images\n- Fast repro of a failing scenario — restart `poetry run app` in a couple of seconds\n\n**When to prefer docker mode (3e fallback):**\n- Testing changes to `Dockerfile`, `docker-compose.yml`, or base images\n- Production-parity smoke tests (exact container env, networking, volumes)\n- CI-equivalent runs where you need the exact image that'll ship\n\n**Note on 3b (copilot auth):** no npm install anywhere. `poetry install` pulls in `claude_agent_sdk`, which ships its own Claude CLI binary — available on `PATH` whenever you run commands via `poetry run` (native) OR whenever the copilot_executor container is built from its Poetry lockfile (docker). The OAuth token extraction still applies (same `refresh_claude_token.sh` call).\n\n**Preamble:** before starting native, run the kill-stray + free-ports block from 3c's \"Native mode also\" subsection.\n\n**1. Start infra only (one-time per session):**\n\n```bash\ncd $PLATFORM_DIR && docker compose --profile local up deps --detach --remove-orphans --build\n```\n\nThis brings up postgres/supabase/redis/rabbitmq/clamav and skips all app services.\n\n**2. Start the backend natively:**\n\n```bash\ncd $BACKEND_DIR && (poetry run app 2>&1 | tee .ign.application.logs) &\n```\n\n`poetry run app` spawns **all** app subprocesses — `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, `database_manager` — inside ONE parent process. No separate containers, no separate terminals. The `.ign.application.logs` prefix is already gitignored.\n\n**3. Wait for the backend on :8006 BEFORE starting the frontend.** This ordering matters — the frontend's `pnpm dev` startup invokes `generate-api-queries`, which fetches `/openapi.json` from the backend. If the backend isn't listening yet, `pnpm dev` fails immediately.\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8006/docs 2>/dev/null)\" = \"200\" ]; then\n    echo \"Backend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\n**4. Start the frontend natively:**\n\n```bash\ncd $FRONTEND_DIR && (pnpm dev 2>&1 | tee .ign.frontend.logs) &\n```\n\n**5. Wait for the frontend on :3000:**\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000 2>/dev/null)\" = \"200\" ]; then\n    echo \"Frontend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\nOnce both are up, skip 3e/3f and go straight to **3g/3h** (feature flags / test user creation).\n\n### 3e. Build and start (docker — fallback)\n\n```bash\ncd $PLATFORM_DIR && docker compose build --no-cache 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker build failed\"; exit 1; fi\n\ncd $PLATFORM_DIR && docker compose up -d 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker compose up failed\"; exit 1; fi\n```\n\n**Note:** If the container appears to be running old code (e.g. missing PR changes), use `docker compose build --no-cache` to force a full rebuild. Docker BuildKit may sometimes reuse cached `COPY` layers from a previous build on a different branch.\n\n**Expected time: 3-8 minutes** for build, 5-10 minutes with `--no-cache`.\n\n### 3f. Wait for services to be ready\n\n```bash\n# Poll until backend and frontend respond\nfor i in $(seq 1 60); do\n  BACKEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8006/docs 2>/dev/null)\n  FRONTEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000 2>/dev/null)\n  if [ \"$BACKEND\" = \"200\" ] && [ \"$FRONTEND\" = \"200\" ]; then\n    echo \"Services ready\"\n    break\n  fi\n  sleep 5\ndone\n```\n\n\n### 3h. Create test user and get auth token\n\nThe platform moved off Supabase auth to Better Auth, embedded in the\nNext.js app at `/api/auth/*`. Signup and sign-in both go through the frontend\nnow, not Kong on :8000 — and `/api/auth/token` mints a backend-API JWT from a\n**session cookie**, it does not accept credentials directly, so sign-in has to\nhappen first to get that cookie.\n\nBetter Auth's default minimum password length is **12 characters** — shorter\nvalues fail signup with `PASSWORD_TOO_SHORT` and every step below degrades\nsilently into an empty token unless you check for it.\n\n```bash\nCOOKIE_JAR=$(mktemp)\ntrap 'rm -f \"$COOKIE_JAR\"' EXIT  # cleans up on early exit too, not just the happy path\n# Via env vars, not `jq --arg`, which would put the password in the process arglist.\nAUTH_PAYLOAD=$(PR_TEST_USER_EMAIL=\"$PR_TEST_USER_EMAIL\" PR_TEST_USER_PASSWORD=\"$PR_TEST_USER_PASSWORD\" \\\n  jq -nc '{email:$ENV.PR_TEST_USER_EMAIL,password:$ENV.PR_TEST_USER_PASSWORD,name:\"PR Test User\"}')\n\n# Signup (idempotent — a real error body means \"already exists\" only if you\n# check; -d passes the payload as an argument, which leaks the password into\n# process listings, so pipe it through stdin with --data-binary @- instead.\n# --noproxy guards against an inherited proxy env var routing the password\n# through a proxy. --max-time bounds it — without one, a server that accepts\n# the connection but never responds hangs setup indefinitely instead of\n# reaching the empty-$TOKEN failure check below).\nSIGNUP_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -X POST 'http://localhost:3000/api/auth/sign-up/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNUP_RESULT\" | grep -qi '\"code\"' && echo \"Signup: $SIGNUP_RESULT\"  # log it — \"already exists\" and \"password too short\" look identical downstream otherwise\n\n# Sign in — sets the better-auth.session_token cookie in $COOKIE_JAR.\n# Capture the body: a failure here (e.g. account exists with a different\n# password) otherwise only shows up as an empty $TOKEN with no explanation.\nSIGNIN_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -c \"$COOKIE_JAR\" -X POST 'http://localhost:3000/api/auth/sign-in/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNIN_RESULT\" | grep -qi '\"code\"' && echo \"Sign-in: $SIGNIN_RESULT\"\n\n# Mint a backend-API JWT from the session cookie\nTOKEN=$(curl -s -b \"$COOKIE_JAR\" 'http://localhost:3000/api/auth/token' | jq -r '.token // \"\"')\n[ -n \"$TOKEN\" ] || { echo \"ERROR: auth setup failed — TOKEN is empty. Check password length (min 12 chars), AUTH_ALLOW_NEW_ACCOUNTS, AUTH_SIGNUP_ALLOWLIST, and rate limiting on /api/auth/*.\"; exit 1; }\n```\n\n**Use this token for ALL API calls:**\n```bash\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/...\n```\n\n### 3i. Disable onboarding for test user\n\nThe frontend redirects to `/onboarding` when the `ONBOARDING_COMPLETE` step is not in `completedSteps`.\nMark it complete via the backend API so every browser test lands on the real feature UI:\n\n```bash\nONBOARDING_RESULT=$(curl -s --max-time 30 -X POST \\\n  \"http://localhost:8006/api/onboarding/step?step=ONBOARDING_COMPLETE\" \\\n  -H \"Authorization: Bearer $TOKEN\")\necho \"Onboarding bypass: $ONBOARDING_RESULT\"\n\n# Verify it took effect\nONBOARDING_STATUS=$(curl -s --max-time 30 \\\n  \"http://localhost:8006/api/onboarding/completed\" \\\n  -H \"Authorization: Bearer $TOKEN\" | jq -r '.is_completed')\necho \"Onboarding completed: $ONBOARDING_STATUS\"\nif [ \"$ONBOARDING_STATUS\" != \"true\" ]; then\n  echo \"ERROR: onboarding bypass failed — browser tests will hit /onboarding instead of the target feature. Investigate before proceeding.\"\n  exit 1\nfi\n```\n\n## Step 4: Run tests\n\n### Service ports reference\n\n| Service | Port | URL |\n|---------|------|-----|\n| Frontend | 3000 | http://localhost:3000 |\n| Backend REST | 8006 | http://localhost:8006 |\n| Supabase Auth (via Kong) | 8000 | http://localhost:8000 |\n| Executor (metrics only) | 8002 | http://localhost:8002 in native mode; not published by Docker Compose |\n| Copilot Executor (metrics only) | 8008 | http://localhost:8008 in native mode; not published by Docker Compose |\n| WebSocket | 8001 | http://localhost:8001 |\n| Database Manager | 8005 | http://localhost:8005 in native mode; not published by Docker Compose. Every method needs `Authorization: Bearer <internal key>` (`backend/util/service_auth.py`) |\n| Redis | 6379 | localhost:6379 |\n| RabbitMQ | 5672 | localhost:5672 |\n\n### API testing\n\nUse `curl` with the auth token for backend API tests. **For EVERY API call that changes state, record before/after values:**\n\n```bash\n# Example: List agents\ncurl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/graphs | jq . | head -20\n\n# Example: Create an agent\ncurl -s -X POST http://localhost:8006/api/graphs \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{...}' | jq .\n\n# Example: Run an agent\ncurl -s -X POST \"http://localhost:8006/api/graphs/{graph_id}/execute\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"data\": {...}}'\n\n# Example: Get execution results\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:8006/api/graphs/{graph_id}/executions/{exec_id}\" | jq .\n```\n\n**State verification pattern (use for EVERY state-changing API call):**\n```bash\n# 1. Record BEFORE state\nBEFORE_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"BEFORE: $BEFORE_STATE\"\n\n# 2. Perform the action\nACTION_RESULT=$(curl -s -X POST ... | jq .)\necho \"ACTION RESULT: $ACTION_RESULT\"\n\n# 3. Record AFTER state\nAFTER_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"AFTER: $AFTER_STATE\"\n\n# 4. Log the comparison\necho \"=== STATE CHANGE VERIFICATION ===\"\necho \"Before: $BEFORE_STATE\"\necho \"After: $AFTER_STATE\"\necho \"Expected change: {describe what should have changed}\"\n```\n\n### Mock-provider pattern (deterministic, $0)\n\nFor timeout/latency/error-handling behavior that would otherwise need a real\nLLM call, point the OpenAI SDK at a local mock instead — it honours\n`OPENAI_BASE_URL`, so a small local Responses-API server can stand in for the\nprovider while everything downstream (executor, credentials, billing) still\nruns for real. This proved out 4/4 test items at $0 in this run. **This\ncovers LLM blocks (`providers.py`) and the Codex block** — the copilot's own\nLLM calls go through `backend/util/clients.py`, which passes `base_url`\nexplicitly and does not read `OPENAI_BASE_URL`, so this trick doesn't reach\ncopilot chat.\n\n```bash\n# In $BACKEND_DIR/.env, point the OpenAI provider at a local mock server\n# that implements the subset of the Responses API you need (e.g. delayed\n# responses to test timeout handling, or a 500 to test error surfacing).\n# Restart the backend after changing this — it's read at startup.\nOPENAI_BASE_URL=http://localhost:{mock_port}/v1\n```\n\n**Use a throwaway/dummy provider credential with the mock, never the system\ncredential** — only the transport is faked, so the credential lookup still\nruns for real and whatever key you configure gets sent as a header to your\nlocal mock server. A dummy key also means the mock server's logs (which may\nend up pasted into a PR comment) can't leak a real one. Aside from the\ncredential, this is safe to use for anything that isn't itself testing model\n*output* quality.\n\nIn **docker mode**, `localhost` inside the backend container isn't reachable\nfrom your host-side mock server — point `OPENAI_BASE_URL` at a\nCompose-reachable hostname instead (or `host.docker.internal` with a\n`host-gateway` entry), and set it before `docker compose up` or restart the\naffected services after changing `$BACKEND_DIR/.env`.\n\nKeep the mock server's own response timeout short — the OpenAI SDK's default\nclient timeout is 600s, so a hung mock stalls every LLM-backed block for that\nlong instead of failing fast.\n\n### Browser testing with agent-browser\n\nPrimary tool — use this wherever `agent-browser` is installed:\n\n```bash\n# Close any existing session\nagent-browser close 2>/dev/null || true\n\n# Use --session-name to persist cookies across navigations\n# This means login only needs to happen once per test session\nagent-browser --session-name pr-test open 'http://localhost:3000/login' --timeout 15000\n\n# Get interactive elements\nagent-browser --session-name pr-test snapshot | grep \"textbox\\|button\"\n\n# Login (read creds from env — set PR_TEST_USER_EMAIL / PR_TEST_USER_PASSWORD or ask the user)\nagent-browser --session-name pr-test fill {email_ref} \"$PR_TEST_USER_EMAIL\"\nagent-browser --session-name pr-test fill {password_ref} \"$PR_TEST_USER_PASSWORD\"\nagent-browser --session-name pr-test click {login_button_ref}\nsleep 5\n\n# Dismiss cookie banner if present\nagent-browser --session-name pr-test click 'text=Accept All' 2>/dev/null || true\n\n# Navigate — cookies are preserved so login persists\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n\n# Take screenshot\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/01-page.png\n\n# Interact with elements\nagent-browser --session-name pr-test fill {ref} \"text\"\nagent-browser --session-name pr-test press \"Enter\"\nagent-browser --session-name pr-test click {ref}\nagent-browser --session-name pr-test click 'text=Button Text'\n\n# Read page content\nagent-browser --session-name pr-test snapshot | grep \"text:\"\n```\n\n**Key pages:**\n- `/copilot` — CoPilot chat (for testing copilot features)\n- `/build` — Agent builder (for testing block/node features)\n- `/build?flowID={id}` — Specific agent in builder\n- `/library` — Agent library (for testing listing/import features)\n- `/library/agents/{id}` — Agent detail with run history\n- `/marketplace` — Marketplace\n\n**Fallback — if `agent-browser` isn't installed on the host, don't let `npx`\ndownload it.** Use `@playwright/test` instead — the package is already in the\nfrontend's `node_modules`, but **the Chromium binary itself is not**;\nPlaywright caches browser binaries separately (`~/.cache/ms-playwright/` by\ndefault) and nothing installs them automatically. Run\n`pnpm exec playwright install chromium` once per host if `chromium.launch()`\nfails looking for an executable, then use it for finer control over timing\n(`waitForSelector`, explicit timeouts) than agent-browser's CLI gives you:\n\n```bash\ncd $FRONTEND_DIR  # required — @playwright/test is only declared here, not at the repo root\n# Check the installer's own exit status, not grep's — piping through grep to\n# drop blank lines would otherwise swallow a real install failure and let\n# chromium.launch() below fail later with a much less clear error.\npnpm exec playwright install chromium\n[ $? -eq 0 ] || { echo \"ERROR: playwright install chromium failed\"; exit 1; }\n\nnode -e \"\nconst { chromium } = require('@playwright/test');\n(async () => {\n  const browser = await chromium.launch();\n  try {\n    const page = await browser.newPage();\n    await page.goto('http://localhost:3000/login');\n    await page.screenshot({ path: '$RESULTS_DIR/01-login.png' });\n  } finally {\n    await browser.close();  // otherwise a goto timeout leaks a headless Chromium process\n  }\n})();\n\"\n```\n\n### Checking logs\n\n**Native mode:** when running via `poetry run app` + `pnpm dev`, all app logs stream to the `.ign.*.logs` files written by the `tee` pipes in 3e-native. `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, and `database_manager` are all subprocesses of the single `poetry run app` parent, so their output is interleaved in `.ign.application.logs`.\n\n```bash\n# Backend (all app subprocesses interleaved)\ntail -f $BACKEND_DIR/.ign.application.logs\n\n# Frontend (Next.js dev server)\ntail -f $FRONTEND_DIR/.ign.frontend.logs\n\n# Filter for errors across either log\ngrep -iE \"error|exception|traceback\" $BACKEND_DIR/.ign.application.logs | tail -20\ngrep -iE \"error|exception|traceback\" $FRONTEND_DIR/.ign.frontend.logs | tail -20\n```\n\n**Docker mode:**\n\n```bash\n# Backend REST server\ndocker logs autogpt_platform-rest_server-1 2>&1 | tail -30\n\n# Executor (runs agent graphs)\ndocker logs autogpt_platform-executor-1 2>&1 | tail -30\n\n# Copilot executor (runs copilot chat sessions)\ndocker logs autogpt_platform-copilot_executor-1 2>&1 | tail -30\n\n# Frontend\ndocker logs autogpt_platform-frontend-1 2>&1 | tail -30\n\n# Filter for errors\ndocker logs autogpt_platform-executor-1 2>&1 | grep -i \"error\\|exception\\|traceback\" | tail -20\n```\n\n### Copilot chat testing\n\nThe copilot uses SSE streaming. To test via API:\n\n```bash\n# Create a session\nSESSION_ID=$(curl -s -X POST 'http://localhost:8006/api/chat/sessions' \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}' | jq -r '.id // .session_id // \"\"')\n\n# Stream a message (SSE - will stream chunks)\ncurl -N -X POST \"http://localhost:8006/api/chat/sessions/$SESSION_ID/stream\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"message\": \"Hello, what can you help me with?\"}' \\\n  --max-time 60 2>/dev/null | head -50\n```\n\nOr test via browser (preferred for UI verification):\n```bash\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n# ... fill chat input and press Enter, wait 20-30s for response\n```\n\n## Step 5: Record results and take screenshots\n\n**Take a screenshot at EVERY significant test step** — before and after interactions, on success, and on failure. This is NON-NEGOTIABLE.\n\n**Required screenshot pattern for each test scenario:**\n```bash\n# BEFORE the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-before.png\n\n# Perform the action...\n\n# AFTER the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-after.png\n```\n\n**Naming convention:**\n```bash\n# Examples:\n# $RESULTS_DIR/01-login-page-before.png\n# $RESULTS_DIR/02-login-page-after.png\n# $RESULTS_DIR/03-credits-page-before.png\n# $RESULTS_DIR/04-credits-purchase-after.png\n# $RESULTS_DIR/05-negative-insufficient-credits.png\n# $RESULTS_DIR/06-error-state.png\n```\n\n**Minimum requirements:**\n- At least TWO screenshots per test scenario (before + after)\n- At least ONE screenshot for each negative test case showing the error state\n- If a test fails, screenshot the failure state AND any error logs visible in the UI\n\n## Step 6: Show results to user with screenshots\n\n**CRITICAL: After all tests complete, you MUST show every screenshot to the user using the Read tool, with an explanation of what each screenshot shows.** This is the most important part of the test report — the user needs to visually verify the results.\n\nFor each screenshot:\n1. Use the `Read` tool to display the PNG file (Claude can read images)\n2. Write a 1-2 sentence explanation below it describing:\n   - What page/state is being shown\n   - What the screenshot proves (which test scenario it validates)\n   - Any notable details visible in the UI\n\nFormat the output like this:\n\n```markdown\n### Screenshot 1: {descriptive title}\n[Read the PNG file here]\n\n**What it shows:** {1-2 sentence explanation of what this screenshot proves}\n\n---\n```\n\nAfter showing all screenshots, output a **detailed** summary table:\n\n| # | Scenario | Result | API Evidence | Screenshot Evidence |\n|---|----------|--------|-------------|-------------------|\n| 1 | {name} | PASS/FAIL | Before: X, After: Y | 01-before.png, 02-after.png |\n| 2 | ... | ... | ... | ... |\n\n**IMPORTANT:** As you show each screenshot and record test results, persist them in shell variables for Step 7:\n\n```bash\n# Build these variables during Step 6 — they are required by Step 7's script\n# NOTE: declare -A requires Bash 4.0+. This is standard on modern systems (macOS ships zsh\n# but Homebrew bash is 5.x; Linux typically has bash 5.x). If running on Bash <4, use a\n# plain variable with a lookup function instead.\ndeclare -A SCREENSHOT_EXPLANATIONS=(\n  [\"01-login-page.png\"]=\"Shows the login page loaded successfully with SSO options visible.\"\n  [\"02-builder-with-block.png\"]=\"The builder canvas displays the newly added block connected to the trigger.\"\n  # ... one entry per screenshot, using the same explanations you showed the user above\n)\n\nTEST_RESULTS_TABLE=\"| 1 | Login flow | PASS | N/A | 01-login-before.png, 02-login-after.png |\n| 2 | Credits purchase | PASS | Before: 100, After: 95 | 03-credits-before.png, 04-credits-after.png |\n| 3 | Insufficient credits (negative) | PASS | Credits: 0, rejected | 05-insufficient-credits-error.png |\"\n# ... one row per test scenario with actual results\n```\n\n## Step 7: Post test report as PR comment with screenshots\n\nPost the report with `gh pr comment --attach`. Each attached file is uploaded to GitHub's own asset store, and any image the body already references by that path is rewritten to point at the uploaded asset. Screenshots never touch a repo branch.\n\n**This step is MANDATORY. Every test run MUST post a PR comment — with the screenshots attached, or an image-free INCOMPLETE report when attachment is unavailable (see the fallback below). Never nothing.**\n\n**Attachments are permanent.** A `user-attachments` asset stays reachable by anyone holding its URL regardless of repo visibility, and deleting the comment does not reliably revoke it — there is no undo. Look at every screenshot for credentials, tokens, or customer data before this step runs.\n\n**Requires `gh` >= 2.99.0** — `--attach` landed there. Distro packages lag (Fedora's rpm tops out at 2.97.0), so check the version you are actually running before building the body:\n\n```bash\n# Too-old gh is not fatal here: the report still gets posted, just without\n# images, through the same fallback path a failed upload takes (below).\nGH_VERSION=$(gh version 2>/dev/null | head -1 | awk '{print $3}')\n# Numeric compare, not `sort -V`: BSD sort on macOS may lack -V, and an empty\n# substitution would silently read as \"too old\" on a perfectly good gh.\nGH_MAJOR=${GH_VERSION%%.*}; GH_REST=${GH_VERSION#*.}; GH_MINOR=${GH_REST%%.*}\nATTACH_OK=0\nATTACH_SKIP_REASON=\"\"\nif [ \"${GH_MAJOR:-0}\" -gt 2 ] 2>/dev/null || { [ \"${GH_MAJOR:-0}\" -eq 2 ] && [ \"${GH_MINOR:-0}\" -ge 99 ]; } 2>/dev/null; then\n  ATTACH_OK=1\nelse\n  ATTACH_SKIP_REASON=\"gh ${GH_VERSION:-not found} is below 2.99.0 — upgrade gh\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\nfi\n```\n\n**CRITICAL — NEVER post a bare directory link like `https://github.com/.../tree/...`.** Every screenshot MUST appear as `![name](url)` inline in the PR comment so reviewers can see them without clicking any links. After posting, the verification step below counts the rewritten asset URLs against the number of screenshots and exits 1 on any shortfall — the test run is incomplete until this passes.\n\n**CRITICAL — NEVER paste absolute local paths or credentials into the PR comment.** Strings like `/Users/…`, `/home/…`, `C:\\…` are useless to every reviewer except you, and a bearer token or JWT copied from the live stack's evidence is a real leak in a public comment. The build block below runs an egress check on the exact bytes about to be posted and aborts on either. The `./01-shot.png` references the attachments use are relative, not local paths, and are rewritten on upload — they are fine. Keep local paths in `$RESULTS_DIR/test-report.md` for yourself; only copy the *content* they reference (excerpts, test names, log lines) into the PR comment, not the path.\n\nBuild the body with **relative image references**, then attach the same paths:\n\n```bash\nREPO=\"Significant-Gravitas/AutoGPT\"\nMAX_ATTACHMENTS=50   # per gh pr comment invocation\n\n# Fail closed: an empty RESULTS_DIR would glob the PR worktree's PNGs into a public comment.\n# gh matches --attach to the body by exact path, so the cd is required; undone at the end.\n[ -n \"$RESULTS_DIR\" ] && [ -d \"$RESULTS_DIR\" ] || { echo \"ERROR: RESULTS_DIR is unset or not a directory: '$RESULTS_DIR'\"; exit 1; }\nSTEP7_ORIG_DIR=$PWD\ncd \"$RESULTS_DIR\" || exit 1\nNULLGLOB_WAS=$(shopt -p nullglob)   # restore the caller's setting, don't force it off\nshopt -s nullglob\nSCREENSHOT_FILES=(*.png)\n$NULLGLOB_WAS\nif [ ${#SCREENSHOT_FILES[@]} -eq 0 ]; then\n  echo \"ERROR: No screenshots found in $RESULTS_DIR. Test run is incomplete.\"\n  exit 1\nfi\n# Over the cap, post the report without images rather than nothing at all.\nif [ ${#SCREENSHOT_FILES[@]} -gt \"$MAX_ATTACHMENTS\" ]; then\n  ATTACH_SKIP_REASON=\"${#SCREENSHOT_FILES[@]} screenshots exceed the ${MAX_ATTACHMENTS}-attachment limit per comment\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\n  ATTACH_OK=0\nfi\n\nIMAGE_MARKDOWN=\"\"\nATTACH_ARGS=()\nfor BASENAME in \"${SCREENSHOT_FILES[@]}\"; do\n  TITLE=$(echo \"${BASENAME%.png}\" | sed 's/^[0-9]*-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')\n  EXPLANATION=\"${SCREENSHOT_EXPLANATIONS[$BASENAME]}\"\n  if [ -z \"$EXPLANATION\" ]; then\n    echo \"ERROR: Missing screenshot explanation for $BASENAME. Add it to SCREENSHOT_EXPLANATIONS in Step ","frontmatter":{"name":"pr-test","description":"E2E manual testing of PRs/branches using docker compose, agent-browser, and API calls. TRIGGER when user asks to manually test a PR, test a feature end-to-end, or run integration tests against a running system.","user-invocable":true,"argument-hint":"[worktree path or PR number] — tests the PR in the given worktree. Optional flags: --fix (auto-fix issues found)","metadata":{"author":"autogpt-team","version":"2.3.0"}},"isInternal":false,"tokens":13302,"sizeBytes":50228},{"name":"SKILL.md","path":".claude/skills/setup-repo/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/setup-repo/SKILL.md","title":"setup-repo","category":"anthropic-skill","format":"markdown","content":"---\nname: setup-repo\ndescription: Initialize a worktree-based repo layout for parallel development. Creates a main worktree, a reviews worktree for PR reviews, and N numbered work branches. Handles .env creation, dependency installation, and branchlet config. TRIGGER when user asks to set up the repo from scratch, initialize worktrees, bootstrap their dev environment, \"setup repo\", \"setup worktrees\", \"initialize dev environment\", \"set up branches\", or when a freshly cloned repo has no sibling worktrees.\nuser-invocable: true\nargs: \"No arguments — interactive setup via prompts.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Repository Setup\n\nThis skill sets up a worktree-based development layout from a freshly cloned repo. It creates:\n- A **main** worktree (the primary checkout)\n- A **reviews** worktree (for PR reviews)\n- **N work branches** (branch1..branchN) for parallel development\n\n## Step 1: Identify the repo\n\nDetermine the repo root and parent directory:\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nREPO_NAME=$(basename \"$ROOT\")\nPARENT=$(dirname \"$ROOT\")\n```\n\nDetect if the repo is already inside a worktree layout by counting sibling worktrees (not just checking the directory name, which could be anything):\n\n```bash\n# Count worktrees that are siblings (live under $PARENT but aren't $ROOT itself)\nSIBLING_COUNT=$(git worktree list --porcelain 2>/dev/null | grep \"^worktree \" | grep -c \"$PARENT/\" || true)\nif [ \"$SIBLING_COUNT\" -gt 1 ]; then\n  echo \"INFO: Existing worktree layout detected at $PARENT ($SIBLING_COUNT worktrees)\"\n  # Use $ROOT as-is; skip renaming/restructuring\nelse\n  echo \"INFO: Fresh clone detected, proceeding with setup\"\nfi\n```\n\n## Step 2: Ask the user questions\n\nUse AskUserQuestion to gather setup preferences:\n\n1. **How many parallel work branches do you need?** (Options: 4, 8, 16, or custom)\n   - These become `branch1` through `branchN`\n2. **Which branch should be the base?** (Options: origin/master, origin/dev, or custom)\n   - All work branches and reviews will start from this\n\n## Step 3: Fetch and set up branches\n\n```bash\ncd \"$ROOT\"\ngit fetch origin\n\n# Create the reviews branch from base (skip if already exists)\nif git show-ref --verify --quiet refs/heads/reviews; then\n  echo \"INFO: Branch 'reviews' already exists, skipping\"\nelse\n  git branch reviews <base-branch>\nfi\n\n# Create numbered work branches from base (skip if already exists)\nfor i in $(seq 1 \"$COUNT\"); do\n  if git show-ref --verify --quiet \"refs/heads/branch$i\"; then\n    echo \"INFO: Branch 'branch$i' already exists, skipping\"\n  else\n    git branch \"branch$i\" <base-branch>\n  fi\ndone\n```\n\n## Step 4: Create worktrees\n\nCreate worktrees as siblings to the main checkout:\n\n```bash\nif [ -d \"$PARENT/reviews\" ]; then\n  echo \"INFO: Worktree '$PARENT/reviews' already exists, skipping\"\nelse\n  git worktree add \"$PARENT/reviews\" reviews\nfi\n\nfor i in $(seq 1 \"$COUNT\"); do\n  if [ -d \"$PARENT/branch$i\" ]; then\n    echo \"INFO: Worktree '$PARENT/branch$i' already exists, skipping\"\n  else\n    git worktree add \"$PARENT/branch$i\" \"branch$i\"\n  fi\ndone\n```\n\n## Step 5: Set up environment files\n\n**Do NOT assume .env files exist.** For each worktree (including main if needed):\n\n1. Check if `.env` exists in the source worktree for each path\n2. If `.env` exists, copy it\n3. If only `.env.default` or `.env.example` exists, copy that as `.env`\n4. If neither exists, warn the user and list which env files are missing\n\nEnv file locations to check (same as the `/worktree` skill — keep these in sync):\n- `autogpt_platform/.env`\n- `autogpt_platform/backend/.env`\n- `autogpt_platform/frontend/.env`\n\n> **Note:** This env copying logic intentionally mirrors the `/worktree` skill's approach. If you update the path list or fallback logic here, update `/worktree` as well.\n\n```bash\nSOURCE=\"$ROOT\"\nWORKTREES=\"reviews\"\nfor i in $(seq 1 \"$COUNT\"); do WORKTREES=\"$WORKTREES branch$i\"; done\n\nFOUND_ANY_ENV=0\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  for envpath in autogpt_platform autogpt_platform/backend autogpt_platform/frontend; do\n    if [ -f \"$SOURCE/$envpath/.env\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env\" \"$TARGET/$envpath/.env\"\n    elif [ -f \"$SOURCE/$envpath/.env.default\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.default — you may need to edit it\"\n    elif [ -f \"$SOURCE/$envpath/.env.example\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.example\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.example — you may need to edit it\"\n    else\n      echo \"WARNING: No .env, .env.default, or .env.example found at $SOURCE/$envpath/\"\n    fi\n  done\ndone\n\nif [ \"$FOUND_ANY_ENV\" -eq 0 ]; then\n  echo \"WARNING: No environment files or templates were found in the source worktree.\"\n  # Use AskUserQuestion to confirm: \"Continue setup without env files?\"\n  # If the user declines, stop here and let them set up .env files first.\nfi\n```\n\n## Step 6: Copy branchlet config\n\nCopy `.branchlet.json` from main to each worktree so branchlet can manage sub-worktrees:\n\n```bash\nif [ -f \"$ROOT/.branchlet.json\" ]; then\n  for wt in $WORKTREES; do\n    cp \"$ROOT/.branchlet.json\" \"$PARENT/$wt/.branchlet.json\"\n  done\nfi\n```\n\n## Step 7: Install dependencies\n\nInstall deps in all worktrees. Run these sequentially per worktree:\n\n```bash\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  echo \"=== Installing deps for $wt ===\"\n  (cd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install) &&\n  (cd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate) &&\n  (cd \"$TARGET/autogpt_platform/frontend\" && pnpm install) &&\n  echo \"=== Done: $wt ===\" ||\n  echo \"=== FAILED: $wt ===\"\ndone\n```\n\nThis is slow. Run in background if possible and notify when complete.\n\n## Step 8: Verify and report\n\nAfter setup, verify and report to the user:\n\n```bash\ngit worktree list\n```\n\nSummarize:\n- Number of worktrees created\n- Which env files were copied vs created from defaults vs missing\n- Any warnings or errors encountered\n\n## Final directory layout\n\n```\nparent/\n  main/              # Primary checkout (already exists)\n  reviews/           # PR review worktree\n  branch1/           # Work branch 1\n  branch2/           # Work branch 2\n  ...\n  branchN/           # Work branch N\n```\n","frontmatter":{"name":"setup-repo","description":"Initialize a worktree-based repo layout for parallel development. Creates a main worktree, a reviews worktree for PR reviews, and N numbered work branches. Handles .env creation, dependency installation, and branchlet config. TRIGGER when user asks to set up the repo from scratch, initialize worktrees, bootstrap their dev environment, \"setup repo\", \"setup worktrees\", \"initialize dev environment\", \"set up branches\", or when a freshly cloned repo has no sibling worktrees.","user-invocable":true,"args":"No arguments — interactive setup via prompts.","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":1732,"sizeBytes":6394},{"name":"AGENTS.md","path":".claude/skills/vercel-react-best-practices/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/AGENTS.md","title":"Vercel-react-best-practices Skill","category":"anthropic-skill","format":"markdown","content":"# Vercel-react-best-practices Skill\n\nPath: `.claude/skills/vercel-react-best-practices/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/AGENTS.md)","tokens":300,"sizeBytes":249},{"name":"advanced-event-handler-refs.md","path":".claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md)","tokens":300,"sizeBytes":281},{"name":"advanced-use-latest.md","path":".claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md)","tokens":300,"sizeBytes":265},{"name":"async-api-routes.md","path":".claude/skills/vercel-react-best-practices/rules/async-api-routes.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-api-routes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md)","tokens":300,"sizeBytes":259},{"name":"async-defer-await.md","path":".claude/skills/vercel-react-best-practices/rules/async-defer-await.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-defer-await.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md)","tokens":300,"sizeBytes":261},{"name":"async-dependencies.md","path":".claude/skills/vercel-react-best-practices/rules/async-dependencies.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md)","tokens":300,"sizeBytes":263},{"name":"async-parallel.md","path":".claude/skills/vercel-react-best-practices/rules/async-parallel.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-parallel.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-parallel.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-parallel.md)","tokens":300,"sizeBytes":255},{"name":"async-suspense-boundaries.md","path":".claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md)","tokens":300,"sizeBytes":277},{"name":"bundle-barrel-imports.md","path":".claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md)","tokens":300,"sizeBytes":269},{"name":"bundle-conditional.md","path":".claude/skills/vercel-react-best-practices/rules/bundle-conditional.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md)","tokens":300,"sizeBytes":263},{"name":"bundle-defer-third-party.md","path":".claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md)","tokens":300,"sizeBytes":275},{"name":"bundle-dynamic-imports.md","path":".claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md)","tokens":300,"sizeBytes":271},{"name":"bundle-preload.md","path":".claude/skills/vercel-react-best-practices/rules/bundle-preload.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-preload.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md)","tokens":300,"sizeBytes":255},{"name":"client-event-listeners.md","path":".claude/skills/vercel-react-best-practices/rules/client-event-listeners.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md)","tokens":300,"sizeBytes":271},{"name":"client-swr-dedup.md","path":".claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md)","tokens":300,"sizeBytes":259},{"name":"js-batch-dom-css.md","path":".claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md)","tokens":300,"sizeBytes":259},{"name":"js-cache-function-results.md","path":".claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md)","tokens":300,"sizeBytes":277},{"name":"js-cache-property-access.md","path":".claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md)","tokens":300,"sizeBytes":275},{"name":"js-cache-storage.md","path":".claude/skills/vercel-react-best-practices/rules/js-cache-storage.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md)","tokens":300,"sizeBytes":259},{"name":"js-combine-iterations.md","path":".claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md)","tokens":300,"sizeBytes":269},{"name":"js-early-exit.md","path":".claude/skills/vercel-react-best-practices/rules/js-early-exit.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-early-exit.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md)","tokens":300,"sizeBytes":253},{"name":"js-hoist-regexp.md","path":".claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md)","tokens":300,"sizeBytes":257},{"name":"js-index-maps.md","path":".claude/skills/vercel-react-best-practices/rules/js-index-maps.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-index-maps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md)","tokens":300,"sizeBytes":253},{"name":"js-length-check-first.md","path":".claude/skills/vercel-react-best-practices/rules/js-length-check-first.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md)","tokens":300,"sizeBytes":269},{"name":"js-min-max-loop.md","path":".claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md)","tokens":300,"sizeBytes":257},{"name":"js-set-map-lookups.md","path":".claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md)","tokens":300,"sizeBytes":263},{"name":"js-tosorted-immutable.md","path":".claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md)","tokens":300,"sizeBytes":269},{"name":"rendering-activity.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-activity.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-activity.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md)","tokens":300,"sizeBytes":263},{"name":"rendering-animate-svg-wrapper.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md)","tokens":300,"sizeBytes":285},{"name":"rendering-conditional-render.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md)","tokens":300,"sizeBytes":283},{"name":"rendering-content-visibility.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md)","tokens":300,"sizeBytes":283},{"name":"rendering-hoist-jsx.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md)","tokens":300,"sizeBytes":265},{"name":"rendering-hydration-no-flicker.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md)","tokens":300,"sizeBytes":287},{"name":"rendering-svg-precision.md","path":".claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md)","tokens":300,"sizeBytes":273},{"name":"rerender-defer-reads.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md)","tokens":300,"sizeBytes":267},{"name":"rerender-dependencies.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md)","tokens":300,"sizeBytes":269},{"name":"rerender-derived-state.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md)","tokens":300,"sizeBytes":271},{"name":"rerender-functional-setstate.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md)","tokens":300,"sizeBytes":283},{"name":"rerender-lazy-state-init.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md)","tokens":300,"sizeBytes":275},{"name":"rerender-memo.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-memo.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-memo.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md)","tokens":300,"sizeBytes":253},{"name":"rerender-transitions.md","path":".claude/skills/vercel-react-best-practices/rules/rerender-transitions.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md)","tokens":300,"sizeBytes":267},{"name":"server-after-nonblocking.md","path":".claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md)","tokens":300,"sizeBytes":275},{"name":"server-cache-lru.md","path":".claude/skills/vercel-react-best-practices/rules/server-cache-lru.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md)","tokens":300,"sizeBytes":259},{"name":"server-cache-react.md","path":".claude/skills/vercel-react-best-practices/rules/server-cache-react.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-cache-react.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md)","tokens":300,"sizeBytes":263},{"name":"server-parallel-fetching.md","path":".claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md)","tokens":300,"sizeBytes":275},{"name":"server-serialization.md","path":".claude/skills/vercel-react-best-practices/rules/server-serialization.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-serialization.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/server-serialization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/server-serialization.md)","tokens":300,"sizeBytes":267},{"name":"SKILL.md","path":".claude/skills/vercel-react-best-practices/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/SKILL.md","title":"vercel-react-best-practices","category":"anthropic-skill","format":"markdown","content":"---\nname: vercel-react-best-practices\ndescription: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.\nlicense: MIT\nmetadata:\n  author: vercel\n  version: \"1.0.0\"\n---\n\n# Vercel React Best Practices\n\nComprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 45 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.\n\n## When to Apply\n\nReference these guidelines when:\n- Writing new React components or Next.js pages\n- Implementing data fetching (client or server-side)\n- Reviewing code for performance issues\n- Refactoring existing React/Next.js code\n- Optimizing bundle size or load times\n\n## Rule Categories by Priority\n\n| Priority | Category | Impact | Prefix |\n|----------|----------|--------|--------|\n| 1 | Eliminating Waterfalls | CRITICAL | `async-` |\n| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |\n| 3 | Server-Side Performance | HIGH | `server-` |\n| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |\n| 5 | Re-render Optimization | MEDIUM | `rerender-` |\n| 6 | Rendering Performance | MEDIUM | `rendering-` |\n| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |\n| 8 | Advanced Patterns | LOW | `advanced-` |\n\n## Quick Reference\n\n### 1. Eliminating Waterfalls (CRITICAL)\n\n- `async-defer-await` - Move await into branches where actually used\n- `async-parallel` - Use Promise.all() for independent operations\n- `async-dependencies` - Use better-all for partial dependencies\n- `async-api-routes` - Start promises early, await late in API routes\n- `async-suspense-boundaries` - Use Suspense to stream content\n\n### 2. Bundle Size Optimization (CRITICAL)\n\n- `bundle-barrel-imports` - Import directly, avoid barrel files\n- `bundle-dynamic-imports` - Use next/dynamic for heavy components\n- `bundle-defer-third-party` - Load analytics/logging after hydration\n- `bundle-conditional` - Load modules only when feature is activated\n- `bundle-preload` - Preload on hover/focus for perceived speed\n\n### 3. Server-Side Performance (HIGH)\n\n- `server-cache-react` - Use React.cache() for per-request deduplication\n- `server-cache-lru` - Use LRU cache for cross-request caching\n- `server-serialization` - Minimize data passed to client components\n- `server-parallel-fetching` - Restructure components to parallelize fetches\n- `server-after-nonblocking` - Use after() for non-blocking operations\n\n### 4. Client-Side Data Fetching (MEDIUM-HIGH)\n\n- `client-swr-dedup` - Use SWR for automatic request deduplication\n- `client-event-listeners` - Deduplicate global event listeners\n\n### 5. Re-render Optimization (MEDIUM)\n\n- `rerender-defer-reads` - Don't subscribe to state only used in callbacks\n- `rerender-memo` - Extract expensive work into memoized components\n- `rerender-dependencies` - Use primitive dependencies in effects\n- `rerender-derived-state` - Subscribe to derived booleans, not raw values\n- `rerender-functional-setstate` - Use functional setState for stable callbacks\n- `rerender-lazy-state-init` - Pass function to useState for expensive values\n- `rerender-transitions` - Use startTransition for non-urgent updates\n\n### 6. Rendering Performance (MEDIUM)\n\n- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element\n- `rendering-content-visibility` - Use content-visibility for long lists\n- `rendering-hoist-jsx` - Extract static JSX outside components\n- `rendering-svg-precision` - Reduce SVG coordinate precision\n- `rendering-hydration-no-flicker` - Use inline script for client-only data\n- `rendering-activity` - Use Activity component for show/hide\n- `rendering-conditional-render` - Use ternary, not && for conditionals\n\n### 7. JavaScript Performance (LOW-MEDIUM)\n\n- `js-batch-dom-css` - Group CSS changes via classes or cssText\n- `js-index-maps` - Build Map for repeated lookups\n- `js-cache-property-access` - Cache object properties in loops\n- `js-cache-function-results` - Cache function results in module-level Map\n- `js-cache-storage` - Cache localStorage/sessionStorage reads\n- `js-combine-iterations` - Combine multiple filter/map into one loop\n- `js-length-check-first` - Check array length before expensive comparison\n- `js-early-exit` - Return early from functions\n- `js-hoist-regexp` - Hoist RegExp creation outside loops\n- `js-min-max-loop` - Use loop for min/max instead of sort\n- `js-set-map-lookups` - Use Set/Map for O(1) lookups\n- `js-tosorted-immutable` - Use toSorted() for immutability\n\n### 8. Advanced Patterns (LOW)\n\n- `advanced-event-handler-refs` - Store event handlers in refs\n- `advanced-use-latest` - useLatest for stable callback refs\n\n## How to Use\n\nRead individual rule files for detailed explanations and code examples:\n\n```\nrules/async-parallel.md\nrules/bundle-barrel-imports.md\nrules/_sections.md\n```\n\nEach rule file contains:\n- Brief explanation of why it matters\n- Incorrect code example with explanation\n- Correct code example with explanation\n- Additional context and references\n\n## Full Compiled Document\n\nFor the complete guide with all rules expanded: `AGENTS.md`\n","frontmatter":{"name":"vercel-react-best-practices","description":"React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.","license":"MIT","metadata":{"author":"vercel","version":"1.0.0"}},"isInternal":false,"tokens":1291,"sizeBytes":5316},{"name":"SKILL.md","path":".claude/skills/worktree/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/worktree/SKILL.md","title":"worktree","category":"anthropic-skill","format":"markdown","content":"---\nname: worktree\ndescription: Set up a new git worktree for parallel development. Creates the worktree, copies .env files, installs dependencies, and generates Prisma client. TRIGGER when user asks to set up a worktree, work on a branch in isolation, or needs a separate environment for a branch or PR.\nuser-invocable: true\nargs: \"[name] — optional worktree name (e.g., 'AutoGPT7'). If omitted, uses next available AutoGPT<N>.\"\nmetadata:\n  author: autogpt-team\n  version: \"3.0.0\"\n---\n\n# Worktree Setup\n\n## Create the worktree\n\nDerive paths from the git toplevel. If a name is provided as argument, use it. Otherwise, check `git worktree list` and pick the next `AutoGPT<N>`.\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nPARENT=$(dirname \"$ROOT\")\n\n# From an existing branch\ngit worktree add \"$PARENT/<NAME>\" <branch-name>\n\n# From a new branch off dev\ngit worktree add -b <new-branch> \"$PARENT/<NAME>\" dev\n```\n\n## Copy environment files\n\nCopy `.env` from the root worktree. Falls back to `.env.default` if `.env` doesn't exist.\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nTARGET=\"$(dirname \"$ROOT\")/<NAME>\"\n\nfor envpath in autogpt_platform/backend autogpt_platform/frontend autogpt_platform; do\n  if [ -f \"$ROOT/$envpath/.env\" ]; then\n    cp \"$ROOT/$envpath/.env\" \"$TARGET/$envpath/.env\"\n  elif [ -f \"$ROOT/$envpath/.env.default\" ]; then\n    cp \"$ROOT/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n  fi\ndone\n```\n\n## Install dependencies\n\n```bash\nTARGET=\"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\ncd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install\ncd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate\ncd \"$TARGET/autogpt_platform/frontend\" && pnpm install\n```\n\nReplace `<NAME>` with the actual worktree name (e.g., `AutoGPT7`).\n\n## Running the app (optional)\n\nBackend uses ports: 8001, 8002, 8003, 8005, 8006, 8007, 8008. Free them first if needed:\n\n```bash\nTARGET=\"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\nfor port in 8001 8002 8003 8005 8006 8007 8008; do\n  lsof -ti :$port | xargs kill -9 2>/dev/null || true\ndone\ncd \"$TARGET/autogpt_platform/backend\" && poetry run app\n```\n\n## CoPilot testing\n\nSDK mode spawns a Claude subprocess — won't work inside Claude Code. Set `CHAT_USE_CLAUDE_AGENT_SDK=false` in `backend/.env` to use baseline mode.\n\n## Cleanup\n\n```bash\n# Replace <NAME> with the actual worktree name (e.g., AutoGPT7)\ngit worktree remove \"$(dirname \"$(git rev-parse --show-toplevel)\")/<NAME>\"\n```\n\n## Alternative: Branchlet (optional)\n\nIf [branchlet](https://www.npmjs.com/package/branchlet) is installed:\n\n```bash\nbranchlet create -n <name> -s <source-branch> -b <new-branch>\n```\n","frontmatter":{"name":"worktree","description":"Set up a new git worktree for parallel development. Creates the worktree, copies .env files, installs dependencies, and generates Prisma client. TRIGGER when user asks to set up a worktree, work on a branch in isolation, or needs a separate environment for a branch or PR.","user-invocable":true,"args":"[name] — optional worktree name (e.g., 'AutoGPT7'). If omitted, uses next available AutoGPT<N>.","metadata":{"author":"autogpt-team","version":"3.0.0"}},"isInternal":false,"tokens":773,"sizeBytes":2670},{"name":"SKILL.md","path":".claude/skills/write-frontend-tests/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/write-frontend-tests/SKILL.md","title":"write-frontend-tests","category":"anthropic-skill","format":"markdown","content":"---\nname: write-frontend-tests\ndescription: \"Analyze the current branch diff against dev, plan integration tests for changed frontend pages/components, and write them. TRIGGER when user asks to write frontend tests, add test coverage, or 'write tests for my changes'.\"\nuser-invocable: true\nargs: \"[base branch] — defaults to dev. Optionally pass a specific base branch to diff against.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Write Frontend Tests\n\nAnalyze the current branch's frontend changes, plan integration tests, and write them.\n\n## References\n\nBefore writing any tests, read the testing rules and conventions:\n\n- `autogpt_platform/frontend/TESTING.md` — testing strategy, file locations, examples\n- `autogpt_platform/frontend/src/tests/AGENTS.md` — detailed testing rules, MSW patterns, decision flowchart\n- `autogpt_platform/frontend/src/tests/integrations/test-utils.tsx` — custom render with providers\n- `autogpt_platform/frontend/src/tests/integrations/vitest.setup.tsx` — MSW server setup\n\n## Step 1: Identify changed frontend files\n\n```bash\nBASE_BRANCH=\"${ARGUMENTS:-dev}\"\ncd autogpt_platform/frontend\n\n# Get changed frontend files (excluding generated, config, and test files)\ngit diff \"$BASE_BRANCH\"...HEAD --name-only -- src/ \\\n  | grep -v '__generated__' \\\n  | grep -v '__tests__' \\\n  | grep -v '\\.test\\.' \\\n  | grep -v '\\.stories\\.' \\\n  | grep -v '\\.spec\\.'\n```\n\nAlso read the diff to understand what changed:\n\n```bash\ngit diff \"$BASE_BRANCH\"...HEAD --stat -- src/\ngit diff \"$BASE_BRANCH\"...HEAD -- src/ | head -500\n```\n\n## Step 2: Categorize changes and find test targets\n\nFor each changed file, determine:\n\n1. **Is it a page?** (`page.tsx`) — these are the primary test targets\n2. **Is it a hook?** (`use*.ts`) — test via the page/component that uses it; avoid direct `renderHook()` tests unless it is a shared reusable hook with standalone business logic\n3. **Is it a component?** (`.tsx` in `components/`) — test via the parent page unless it's complex enough to warrant isolation\n4. **Is it a helper?** (`helpers.ts`, `utils.ts`) — unit test directly if pure logic\n\n**Priority order:**\n\n1. Pages with new/changed data fetching or user interactions\n2. Components with complex internal logic (modals, forms, wizards)\n3. Shared hooks with standalone business logic when UI-level coverage is impractical\n4. Pure helper functions\n\nSkip: styling-only changes, type-only changes, config changes.\n\n## Step 3: Check for existing tests\n\nFor each test target, check if tests already exist:\n\n```bash\n# For a page at src/app/(platform)/library/page.tsx\nls src/app/\\(platform\\)/library/__tests__/ 2>/dev/null\n\n# For a component at src/app/(platform)/library/components/AgentCard/AgentCard.tsx\nls src/app/\\(platform\\)/library/components/AgentCard/__tests__/ 2>/dev/null\n```\n\nNote which targets have no tests (need new files) vs which have tests that need updating.\n\n## Step 4: Identify API endpoints used\n\nFor each test target, find which API hooks are used:\n\n```bash\n# Find generated API hook imports in the changed files\ngrep -rn 'from.*__generated__/endpoints' src/app/\\(platform\\)/library/\ngrep -rn 'use[A-Z].*V[12]' src/app/\\(platform\\)/library/\n```\n\nFor each API hook found, locate the corresponding MSW handler:\n\n```bash\n# If the page uses useGetV2ListLibraryAgents, find its MSW handlers\ngrep -rn 'getGetV2ListLibraryAgents.*Handler' src/app/api/__generated__/endpoints/library/library.msw.ts\n```\n\nList every MSW handler you will need (200 for happy path, 4xx for error paths).\n\n## Step 5: Write the test plan\n\nBefore writing code, output a plan as a numbered list:\n\n```\nTest plan for [branch name]:\n\n1. src/app/(platform)/library/__tests__/main.test.tsx (NEW)\n   - Renders page with agent list (MSW 200)\n   - Shows loading state\n   - Shows error state (MSW 422)\n   - Handles empty agent list\n\n2. src/app/(platform)/library/__tests__/search.test.tsx (NEW)\n   - Filters agents by search query\n   - Shows no results message\n   - Clears search\n\n3. src/app/(platform)/library/components/AgentCard/__tests__/AgentCard.test.tsx (UPDATE)\n   - Add test for new \"duplicate\" action\n```\n\nPresent this plan to the user. Wait for confirmation before proceeding. If the user has feedback, adjust the plan.\n\n## Step 6: Write the tests\n\nFor each test file in the plan, follow these conventions:\n\n### File structure\n\n```tsx\nimport { render, screen, waitFor } from \"@/tests/integrations/test-utils\";\nimport { server } from \"@/mocks/mock-server\";\n// Import MSW handlers for endpoints the page uses\nimport {\n  getGetV2ListLibraryAgentsMockHandler200,\n  getGetV2ListLibraryAgentsMockHandler422,\n} from \"@/app/api/__generated__/endpoints/library/library.msw\";\n// Import the component under test\nimport LibraryPage from \"../page\";\n\ndescribe(\"LibraryPage\", () => {\n  test(\"renders agent list from API\", async () => {\n    server.use(getGetV2ListLibraryAgentsMockHandler200());\n\n    render(<LibraryPage />);\n\n    expect(await screen.findByText(/all workflows/i)).toBeDefined();\n  });\n\n  test(\"shows error state on API failure\", async () => {\n    server.use(getGetV2ListLibraryAgentsMockHandler422());\n\n    render(<LibraryPage />);\n\n    expect(await screen.findByText(/error/i)).toBeDefined();\n  });\n});\n```\n\n### Rules\n\n- Use `render()` from `@/tests/integrations/test-utils` (NOT from `@testing-library/react` directly)\n- Use `server.use()` to set up MSW handlers BEFORE rendering\n- Use `findBy*` (async) for elements that appear after data fetching — NOT `getBy*`\n- Use `getBy*` only for elements that are immediately present in the DOM\n- Use `screen` queries — do NOT destructure from `render()`\n- Use `waitFor` when asserting side effects or state changes after interactions\n- Import `fireEvent` or `userEvent` from the test-utils for interactions\n- Do NOT mock internal hooks or functions — mock at the API boundary via MSW\n- Prefer Orval-generated MSW handlers and response builders over hand-built API response objects\n- Do NOT use `act()` manually — `render` and `fireEvent` handle it\n- Keep tests focused: one behavior per test\n- Use descriptive test names that read like sentences\n\n### Test location\n\n```\n# For pages: __tests__/ next to page.tsx\nsrc/app/(platform)/library/__tests__/main.test.tsx\n\n# For complex standalone components: __tests__/ inside component folder\nsrc/app/(platform)/library/components/AgentCard/__tests__/AgentCard.test.tsx\n\n# For pure helpers: co-located .test.ts\nsrc/app/(platform)/library/helpers.test.ts\n```\n\n### Custom MSW overrides\n\nWhen the auto-generated faker data is not enough, override with specific data:\n\n```tsx\nimport { http, HttpResponse } from \"msw\";\n\nserver.use(\n  http.get(\"http://localhost:3000/api/proxy/api/v2/library/agents\", () => {\n    return HttpResponse.json({\n      agents: [{ id: \"1\", name: \"Test Agent\", description: \"A test agent\" }],\n      pagination: { total_items: 1, total_pages: 1, page: 1, page_size: 10 },\n    });\n  }),\n);\n```\n\nUse the proxy URL pattern: `http://localhost:3000/api/proxy/api/v{version}/{path}` — this matches the MSW base URL configured in `orval.config.ts`.\n\n## Step 7: Run and verify\n\nAfter writing all tests:\n\n```bash\ncd autogpt_platform/frontend\npnpm test:unit --reporter=verbose\n```\n\nIf tests fail:\n\n1. Read the error output carefully\n2. Fix the test (not the source code, unless there is a genuine bug)\n3. Re-run until all pass\n\nThen run the full checks:\n\n```bash\npnpm format\npnpm lint\npnpm types\n```\n","frontmatter":{"name":"write-frontend-tests","description":"Analyze the current branch diff against dev, plan integration tests for changed frontend pages/components, and write them. TRIGGER when user asks to write frontend tests, add test coverage, or 'write tests for my changes'.","user-invocable":true,"args":"[base branch] — defaults to dev. Optionally pass a specific base branch to diff against.","metadata":{"author":"autogpt-team","version":"1.0.0"}},"isInternal":false,"tokens":1865,"sizeBytes":7432},{"name":"agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json","path":"autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_00fdd42c-a14c-4d19-a567-65374ea0e87f.json)","tokens":300,"sizeBytes":282},{"name":"agent_31daf49d-31d3-476b-aa4c-099abc59b458.json","path":"autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_31daf49d-31d3-476b-aa4c-099abc59b458.json)","tokens":300,"sizeBytes":282},{"name":"agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json","path":"autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_415b7352-0dc6-4214-9d87-0ad3751b711d.json)","tokens":300,"sizeBytes":282},{"name":"agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json","path":"autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_516d813b-d1bc-470f-add7-c63a4b2c2bad.json)","tokens":300,"sizeBytes":282},{"name":"agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json","path":"autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_55d40473-0f31-4ada-9e40-d3a7139fcbd4.json)","tokens":300,"sizeBytes":282},{"name":"agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json","path":"autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_6e16e65a-ad34-4108-b4fd-4a23fced5ea2.json)","tokens":300,"sizeBytes":282},{"name":"agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json","path":"autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a03b0d8c-4751-43d6-a54e-c3b7856ba4e3.json)","tokens":300,"sizeBytes":282},{"name":"agent_a548e507-09a7-4b30-909c-f63fcda10fff.json","path":"autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_a548e507-09a7-4b30-909c-f63fcda10fff.json)","tokens":300,"sizeBytes":282},{"name":"agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json","path":"autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b6f6f0d3-49f4-4e3b-8155-ffe9141b32c0.json)","tokens":300,"sizeBytes":282},{"name":"agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json","path":"autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_b8ceb480-a7a2-4c90-8513-181a49f7071f.json)","tokens":300,"sizeBytes":282},{"name":"agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json","path":"autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_c775f60d-b99f-418b-8fe0-53172258c3ce.json)","tokens":300,"sizeBytes":282},{"name":"agent_d85882b8-633f-44ce-a315-c20a8c123d19.json","path":"autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_d85882b8-633f-44ce-a315-c20a8c123d19.json)","tokens":300,"sizeBytes":282},{"name":"agent_e437cc95-e671-489d-b915-76561fba8c7f.json","path":"autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e437cc95-e671-489d-b915-76561fba8c7f.json)","tokens":300,"sizeBytes":282},{"name":"agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json","path":"autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_e7bb29a1-23c7-4fee-aa3b-5426174b8c52.json)","tokens":300,"sizeBytes":282},{"name":"agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json","path":"autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_eafa21d3-bf14-4f63-a97f-a5ee41df83b3.json)","tokens":300,"sizeBytes":282},{"name":"agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json","path":"autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_f2cc74bb-f43f-4395-9c35-ecb30b5b4fc9.json)","tokens":300,"sizeBytes":282},{"name":"agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json","path":"autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/agent_fc2c9976-0962-4625-a27b-d316573a9e7f.json)","tokens":300,"sizeBytes":282},{"name":"calculator-agent.json","path":"autogpt_platform/backend/agents/calculator-agent.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/calculator-agent.json","title":"Agents Skill","category":"anthropic-skill","format":"json","content":"# Agents Skill\n\nPath: `autogpt_platform/backend/agents/calculator-agent.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/agents/calculator-agent.json)","tokens":300,"sizeBytes":230},{"name":"delegation_preamble_cases.json","path":"autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json","title":"Tools Skill","category":"anthropic-skill","format":"json","content":"# Tools Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/delegation_preamble_cases.json)","tokens":300,"sizeBytes":277},{"name":"IDEAS.md","path":"autogpt_platform/backend/backend/copilot/tools/IDEAS.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/IDEAS.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"# Tools Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/IDEAS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/IDEAS.md)","tokens":300,"sizeBytes":233},{"name":"web_search_sonar_responses.json","path":"autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json","title":"Testdata Skill","category":"anthropic-skill","format":"json","content":"# Testdata Skill\n\nPath: `autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/backend/copilot/tools/testdata/web_search_sonar_responses.json)","tokens":300,"sizeBytes":300},{"name":"README.md","path":"autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md","title":"Webapp-testing Skill","category":"anthropic-skill","format":"markdown","content":"# Webapp-testing Skill\n\nPath: `autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/test/fixtures/skills/webapp-testing/README.md)","tokens":300,"sizeBytes":272},{"name":"SKILL.md","path":"autogpt_platform/backend/test/fixtures/skills/webapp-testing/SKILL.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/backend/test/fixtures/skills/webapp-testing/SKILL.md","title":"webapp-testing","category":"anthropic-skill","format":"markdown","content":"---\nname: webapp-testing\ndescription: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.\nlicense: Complete terms in LICENSE.txt\n---\n\n# Web Application Testing\n\nTo test local web applications, write native Python Playwright scripts.\n\n**Helper Scripts Available**:\n- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)\n\n**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.\n\n## Decision Tree: Choosing Your Approach\n\n```\nUser task → Is it static HTML?\n    ├─ Yes → Read HTML file directly to identify selectors\n    │         ├─ Success → Write Playwright script using selectors\n    │         └─ Fails/Incomplete → Treat as dynamic (below)\n    │\n    └─ No (dynamic webapp) → Is the server already running?\n        ├─ No → Run: python scripts/with_server.py --help\n        │        Then use the helper + write simplified Playwright script\n        │\n        └─ Yes → Reconnaissance-then-action:\n            1. Navigate and wait for networkidle\n            2. Take screenshot or inspect DOM\n            3. Identify selectors from rendered state\n            4. Execute actions with discovered selectors\n```\n\n## Example: Using with_server.py\n\nTo start a server, run `--help` first, then use the helper:\n\n**Single server:**\n```bash\npython scripts/with_server.py --server \"npm run dev\" --port 5173 -- python your_automation.py\n```\n\n**Multiple servers (e.g., backend + frontend):**\n```bash\npython scripts/with_server.py \\\n  --server \"cd backend && python server.py\" --port 3000 \\\n  --server \"cd frontend && npm run dev\" --port 5173 \\\n  -- python your_automation.py\n```\n\nTo create an automation script, include only Playwright logic (servers are managed automatically):\n```python\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode\n    page = browser.new_page()\n    page.goto('http://localhost:5173') # Server already running and ready\n    page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute\n    # ... your automation logic\n    browser.close()\n```\n\n## Reconnaissance-Then-Action Pattern\n\n1. **Inspect rendered DOM**:\n   ```python\n   page.screenshot(path='/tmp/inspect.png', full_page=True)\n   content = page.content()\n   page.locator('button').all()\n   ```\n\n2. **Identify selectors** from inspection results\n\n3. **Execute actions** using discovered selectors\n\n## Common Pitfall\n\n❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps\n✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection\n\n## Best Practices\n\n- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. \n- Use `sync_playwright()` for synchronous scripts\n- Always close the browser when done\n- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs\n- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`\n\n## Reference Files\n\n- **examples/** - Examples showing common patterns:\n  - `element_discovery.py` - Discovering buttons, links, and inputs on a page\n  - `static_html_automation.py` - Using file:// URLs for local HTML\n  - `console_logging.py` - Capturing console logs during automation","frontmatter":{"name":"webapp-testing","description":"Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","license":"Complete terms in LICENSE.txt"},"isInternal":false,"tokens":881,"sizeBytes":3913},{"name":"capacity.sh","path":".claude/skills/orchestrate/scripts/capacity.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/capacity.sh","title":"Claude Directive: capacity.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: capacity.sh\n\nPath: `.claude/skills/orchestrate/scripts/capacity.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/capacity.sh)","tokens":300,"sizeBytes":233},{"name":"classify-pane.sh","path":".claude/skills/orchestrate/scripts/classify-pane.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/classify-pane.sh","title":"Claude Directive: classify-pane.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: classify-pane.sh\n\nPath: `.claude/skills/orchestrate/scripts/classify-pane.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/classify-pane.sh)","tokens":300,"sizeBytes":248},{"name":"find-spare.sh","path":".claude/skills/orchestrate/scripts/find-spare.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/find-spare.sh","title":"Claude Directive: find-spare.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: find-spare.sh\n\nPath: `.claude/skills/orchestrate/scripts/find-spare.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/find-spare.sh)","tokens":300,"sizeBytes":239},{"name":"notify.sh","path":".claude/skills/orchestrate/scripts/notify.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/notify.sh","title":"Claude Directive: notify.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: notify.sh\n\nPath: `.claude/skills/orchestrate/scripts/notify.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/notify.sh)","tokens":300,"sizeBytes":227},{"name":"poll-cycle.sh","path":".claude/skills/orchestrate/scripts/poll-cycle.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/poll-cycle.sh","title":"Claude Directive: poll-cycle.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: poll-cycle.sh\n\nPath: `.claude/skills/orchestrate/scripts/poll-cycle.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/poll-cycle.sh)","tokens":300,"sizeBytes":239},{"name":"recycle-agent.sh","path":".claude/skills/orchestrate/scripts/recycle-agent.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/recycle-agent.sh","title":"Claude Directive: recycle-agent.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: recycle-agent.sh\n\nPath: `.claude/skills/orchestrate/scripts/recycle-agent.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/recycle-agent.sh)","tokens":300,"sizeBytes":248},{"name":"run-loop.sh","path":".claude/skills/orchestrate/scripts/run-loop.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/run-loop.sh","title":"Claude Directive: run-loop.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: run-loop.sh\n\nPath: `.claude/skills/orchestrate/scripts/run-loop.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/run-loop.sh)","tokens":300,"sizeBytes":233},{"name":"spawn-agent.sh","path":".claude/skills/orchestrate/scripts/spawn-agent.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/spawn-agent.sh","title":"Claude Directive: spawn-agent.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: spawn-agent.sh\n\nPath: `.claude/skills/orchestrate/scripts/spawn-agent.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/spawn-agent.sh)","tokens":300,"sizeBytes":242},{"name":"status.sh","path":".claude/skills/orchestrate/scripts/status.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/status.sh","title":"Claude Directive: status.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: status.sh\n\nPath: `.claude/skills/orchestrate/scripts/status.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/status.sh)","tokens":300,"sizeBytes":227},{"name":"verify-complete.sh","path":".claude/skills/orchestrate/scripts/verify-complete.sh","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/verify-complete.sh","title":"Claude Directive: verify-complete.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: verify-complete.sh\n\nPath: `.claude/skills/orchestrate/scripts/verify-complete.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/orchestrate/scripts/verify-complete.sh)","tokens":300,"sizeBytes":254},{"name":"perf_compare.py","path":".claude/skills/perf-compare/scripts/perf_compare.py","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/perf_compare.py","title":"Claude Directive: perf_compare.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: perf_compare.py\n\nPath: `.claude/skills/perf-compare/scripts/perf_compare.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/perf_compare.py)","tokens":300,"sizeBytes":247},{"name":"test_perf_compare.py","path":".claude/skills/perf-compare/scripts/test_perf_compare.py","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/test_perf_compare.py","title":"Claude Directive: test_perf_compare.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: test_perf_compare.py\n\nPath: `.claude/skills/perf-compare/scripts/test_perf_compare.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/test_perf_compare.py)","tokens":300,"sizeBytes":262},{"name":"CLAUDE.md","path":"autogpt_platform/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/CLAUDE.md","title":"autogpt_platform - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# autogpt_platform - Claude Agent Guidelines & System Prompt\n\nPath: `autogpt_platform/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/CLAUDE.md)","tokens":300,"sizeBytes":222},{"name":"CLAUDE.md","path":"classic/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/CLAUDE.md","title":"classic - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# classic - Claude Agent Guidelines & System Prompt\n\nPath: `classic/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/CLAUDE.md)","tokens":300,"sizeBytes":195},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/CLAUDE.md)","tokens":300,"sizeBytes":169},{"name":"CLAUDE.md","path":"docs/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/CLAUDE.md","title":"docs - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# docs - Claude Agent Guidelines & System Prompt\n\nPath: `docs/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/CLAUDE.md)","tokens":300,"sizeBytes":186},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.github/copilot-instructions.md)","tokens":300,"sizeBytes":201},{"name":"README.md","path":"classic/original_autogpt/autogpt/agents/README.md","rawUrl":"https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/original_autogpt/autogpt/agents/README.md","title":"Subagent: readme","category":"subagent-persona","format":"markdown","content":"# Subagent: readme\n\nPath: `classic/original_autogpt/autogpt/agents/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/classic/original_autogpt/autogpt/agents/README.md)","tokens":300,"sizeBytes":226}],"systemPromptSnippet":"<yakaai_skills repo=\"Significant-Gravitas/AutoGPT\">\n<!-- File: AGENTS.md (Tokens: ~300 | Category: root-instruction) -->\n# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/AGENTS.md)\n\n<!-- File: autogpt_platform/AGENTS.md (Tokens: ~300 | Category: root-instruction) -->\n# autogpt_platform - AI Agent Protocol & Instructions\n\nPath: `autogpt_platform/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/autogpt_platform/AGENTS.md)\n\n<!-- File: docs/AGENTS.md (Tokens: ~300 | Category: root-instruction) -->\n# docs - AI Agent Protocol & Instructions\n\nPath: `docs/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/docs/AGENTS.md)\n\n<!-- File: .claude/skills/open-pr/SKILL.md (Tokens: ~1317 | Category: anthropic-skill) -->\n---\nname: open-pr\ndescription: Open a pull request with proper PR template, test coverage, and review workflow. Guides agents through creating a PR that follows repo conventions, ensures existing behaviors aren't broken, covers new behaviors with tests, and handles review via bot when local testing isn't possible. TRIGGER when user asks to \"open a PR\", \"create a PR\", \"make a PR\", \"submit a PR\", \"open pull request\", \"push and create PR\", or any variation of opening/submitting a pull request.\nuser-invocable: true\nargs: \"[base-branch] — optional target branch (defaults to dev).\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Open a Pull Request\n\n## Step 1: Pre-flight checks\n\nBefore opening the PR:\n\n1. Ensure all changes are committed\n2. Ensure the branch is pushed to the remote (`git push -u origin <branch>`)\n3. Run linters/formatters across the whole repo (not just changed files) and commit any fixes\n\n## Step 2: Test coverage\n\n**This is critical.** Before opening the PR, verify:\n\n### Existing behavior is not broken\n- Identify which modules/components your changes touch\n- Run the existing test suites for those areas\n- If tests fail, fix them before opening the PR — do not open a PR with known regressions\n\n### New behavior has test coverage\n- Every new feature, endpoint, or behavior change needs tests\n- If you added a new block, add tests for that block\n- If you changed API behavior, add or update API tests\n- If you changed frontend behavior, verify it doesn't break existing flows\n\nIf you cannot run the full test suite locally, note which tests you ran and which you couldn't in the test plan.\n\n## Step 3: Create the PR using the repo template\n\nRead the canonical PR template at `.github/PULL_REQUEST_TEMPLATE.md` and use it **verbatim** as your PR body:\n\n1. Read the template: `cat .github/PULL_REQUEST_TEMPLATE.md`\n2. Preserve the exact section titles and formatting, including:\n   - `### Why / What / How`\n   - `### Changes 🏗️`\n   - `### Checklist 📋`\n3. Replace HTML comment prompts (`<!-- ... -->`) with actual content; do not leave them in\n4. **Do not pre-check boxes** — leave all checkboxes as `- [ ]` until each step is actually completed\n5. Do not alter the template structure, rename sections, or remove any checklist items\n\n**PR title must use conventional commit format** (e.g., `feat(backend): add new block`, `fix(frontend): resolve routing bug`, `dx(skills): update PR workflow`). See CLAUDE.md for the full list of scopes.\n\nUse `gh pr create` with the base branch (defaults to `dev` if no `[base-branch]` was provided). Use `--body-file` to avoid shell interpretation of backticks and special characters:\n\n```bash\nBASE_BRANCH=\"${BASE_BRANCH:-dev}\"\nPR_BODY=$(mktemp)\ncat > \"$PR_BODY\" << 'PREOF'\n<filled-in template from .github/PULL_REQUEST_TEMPLATE.md>\nPREOF\ngh pr create --base \"$BASE_BRANCH\" --title \"<type>(scope): short description\" --body-file \"$PR_BODY\"\nrm \"$PR_BODY\"\n```\n\n## Step 4: Review workflow\n\n### If you have a workspace that allows testing (docker, running backend, etc.)\n- Run `/pr-test` to do E2E manual testing of the PR using docker compose, agent-browser, and API calls. This is the most thorough way to validate your changes before review.\n- After testing, run `/pr-review` to self-review the PR for correctness, security, code quality, and testing gaps before requesting human review.\n\n### If you do NOT have a workspace that allows testing\nThis is common for agents running in worktrees without a full stack. In this case:\n\n1. Run `/pr-review` locally to catch obvious issues before pushing\n2. **Comment `/review` on the PR** after creating it to trigger the review bot\n3. **Poll for the review** rather than blindly waiting — check for new review comments every 30 seconds using `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate` and the GraphQL inline threads query. The bot typically responds within 30 minutes, but polling lets the agent react as soon as it arrives.\n4. Do NOT proceed or merge until the bot review comes back — but bound the\n   wait at **60 minutes** from the `/review` comment, the same budget\n   `/pr-address` and `/pr-polish` use. If nothing has arrived by then, stop\n   waiting and tell the user the requested review never came. Never block\n   indefinitely on a bot.\n5. Address any issues the bot raises — use `/pr-address` which has a full polling loop with CI + comment tracking\n\n```bash\n# After creating the PR:\nPR_NUMBER=$(gh pr view --json number -q .number)\ngh pr comment \"$PR_NUMBER\" --body \"/review\"\n# Then use /pr-address to poll for and address the review when it arrives\n```\n\n## Step 5: Address review feedback\n\nOnce the review bot or human reviewers leave comments:\n- Run `/pr-address` to address review comments. It will loop until CI is green and all comments are resolved.\n- Do not merge without human approval.\n\n## Related skills\n\n| Skill | When to use |\n|---|---|\n| `/pr-test` | E2E testing with docker compose, agent-browser, API calls — use when you have a running workspace |\n| `/pr-review` | Review for correctness, security, code quality — use before requesting human review |\n| `/pr-address` | Address reviewer comments and loop until CI green — use after reviews come in; it can also post the `/review` trigger itself if the PR has no review yet |\n\n## Step 6: Post-creation\n\nAfter the PR is created and review is triggered:\n- Share the PR URL with the user\n- If waiting on the review bot, let the user know the expected wait time (~30 min)\n- Do not merge without human approval\n\n<!-- File: .claude/skills/orchestrate/SKILL.md (Tokens: ~9304 | Category: anthropic-skill) -->\n---\nname: orchestrate\ndescription: \"Meta-agent supervisor that manages a fleet of Claude Code agents running in tmux windows. Auto-discovers spare worktrees, spawns agents, monitors state, kicks idle agents, approves safe confirmations, and recycles worktrees when done. TRIGGER when user asks to supervise agents, run parallel tasks, manage worktrees, check agent status, or orchestrate parallel work.\"\nuser-invocable: true\nargument-hint: \"any free text — e.g. 'start 3 agents on X Y Z', 'show status', 'add task: implement feature A', 'stop', 'how many are free?'\"\nmetadata:\n  author: autogpt-team\n  version: \"6.0.0\"\n---\n\n# Orchestrate — Agent Fleet Supervisor\n\nOne tmux session, N windows — each window is one agent working in its own worktree. Speak naturally; Claude maps your intent to the right scripts.\n\n## Scripts\n\n```bash\nSKILLS_DIR=$(git rev-parse --show-toplevel)/.claude/skills/orchestrate/scripts\nSTATE_FILE=~/.claude/orchestrator-state.json\n```\n\n| Script | Purpose |\n|---|---|\n| `find-spare.sh [REPO_ROOT]` | List free worktrees — one `PATH BRANCH` per line |\n| `spawn-agent.sh SESSION PATH SPARE NEW_BRANCH OBJECTIVE [PR_NUMBER] [STEPS...]` | Create window + checkout branch + launch claude + send task. **Stdout: `SESSION:WIN` only** |\n| `recycle-agent.sh WINDOW PATH SPARE_BRANCH` | Kill window + restore spare branch |\n| `run-loop.sh` | **Mechanical babysitter** — idle restart + dialog approval + recycle on ORCHESTRATOR:DONE + supervisor health check + all-done notification |\n| `verify-complete.sh WINDOW` | Verify PR is done: checkpoints ✓ + 0 unresolved threads + CI green + no fresh CHANGES_REQUESTED. Repo auto-derived from state file `.repo` or git remote. |\n| `notify.sh MESSAGE` | Send notification via Discord webhook (env `DISCORD_WEBHOOK_URL` or state `.discord_webhook`), macOS notification center, and stdout |\n| `capacity.sh [REPO_ROOT]` | Print available + in-use worktrees |\n| `status.sh` | Print fleet status + live pane commands |\n| `poll-cycle.sh` | One monitoring cycle — classifies panes, tracks checkpoints, returns JSON action array |\n| `classify-pane.sh WINDOW` | Classify one pane state |\n\n## Supervision model\n\n```\nOrchestrating Claude (this Claude session — IS the supervisor)\n  └── Reads pane output, checks CI, intervenes with targeted guidance\n        run-loop.sh (separate tmux window, every 30s)\n          └── Mechanical only: idle restart, dialog approval, recycle on ORCHESTRATOR:DONE\n```\n\n**You (the orchestrating Claude)** are the supervisor. After spawning agents, stay in this conversation and actively monitor: poll each agent's pane every 2-3 minutes, check CI, nudge stalled agents, and verify completions. Do not spawn a separate supervisor Claude window — it loses context, is hard to observe, and compounds context compression problems.\n\n**run-loop.sh** is the mechanical layer — zero tokens, handles things that need no judgment: restart crashed agents, press Enter on dialogs, recycle completed worktrees (only after `verify-complete.sh` passes).\n\n## Checkpoint protocol\n\nAgents output checkpoints as they complete each required step:\n\n```\nCHECKPOINT:<step-name>\n```\n\nRequired steps are passed as args to `spawn-agent.sh` (e.g. `pr-address pr-test`). `run-loop.sh` will not recycle a window until all required checkpoints are found in the pane output. If `verify-complete.sh` fails, the agent is re-briefed automatically.\n\n## Worktree lifecycle\n\n```text\nspare/N branch  →  spawn-agent.sh (--session-id UUID)  →  window + feat/branch + claude running\n                                                                 ↓\n                                               CHECKPOINT:<step> (as steps complete)\n                                                                 ↓\n                                                        ORCHESTRATOR:DONE\n                                                                 ↓\n                                    verify-complete.sh: checkpoints ✓ + 0 threads + CI green + no fresh CHANGES_REQUESTED\n                                                                 ↓\n                                              state → \"done\", notify, window KEPT OPEN\n                                                                 ↓\n                              user/orchestrator explicitly requests recycle\n                                                                 ↓\n                                         recycle-agent.sh → spare/N (free again)\n```\n\n**Windows are never auto-killed.** The worktree stays on its branch, the session stays alive. The agent is done working but the window, git state, and Claude session are all preserved until you choose to recycle.\n\n**To resume a done or crashed session:**\n```bash\n# Resume by stored session ID (preferred — exact session, full context)\nclaude --resume SESSION_ID --permission-mode bypassPermissions\n\n# Or resume most recent session in that worktree directory\ncd /path/to/worktree && claude --continue --permission-mode bypassPermissions\n```\n\n**To manually recycle when ready:**\n```bash\nbash ~/.claude/orchestrator/scripts/recycle-agent.sh SESSION:WIN WORKTREE_PATH spare/N\n# Then update state:\njq --arg w \"SESSION:WIN\" '.agents |= map(if .window == $w then .state = \"recycled\" else . end)' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## State file (`~/.claude/orchestrator-state.json`)\n\nNever committed to git. You maintain this file directly using `jq` + atomic writes (`.tmp` → `mv`).\n\n```json\n{\n  \"active\": true,\n  \"tmux_session\": \"autogpt1\",\n  \"idle_threshold_seconds\": 300,\n  \"loop_window\": \"autogpt1:5\",\n  \"repo\": \"Significant-Gravitas/AutoGPT\",\n  \"discord_webhook\": \"https://discord.com/api/webhooks/...\",\n  \"last_poll_at\": 0,\n  \"agents\": [\n    {\n      \"window\": \"autogpt1:3\",\n      \"worktree\": \"AutoGPT6\",\n      \"worktree_path\": \"/path/to/AutoGPT6\",\n      \"spare_branch\": \"spare/6\",\n      \"branch\": \"feat/my-feature\",\n      \"objective\": \"Implement X and open a PR\",\n      \"pr_number\": \"12345\",\n      \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"steps\": [\"pr-address\", \"pr-test\"],\n      \"checkpoints\": [\"pr-address\"],\n      \"state\": \"running\",\n      \"last_output_hash\": \"\",\n      \"last_seen_at\": 0,\n      \"spawned_at\": 0,\n      \"idle_since\": 0,\n      \"revision_count\": 0,\n      \"last_rebriefed_at\": 0\n    }\n  ]\n}\n```\n\nTop-level optional fields:\n- `repo` — GitHub `owner/repo` for CI/thread checks. Auto-derived from git remote if omitted.\n- `discord_webhook` — Discord webhook URL for completion notifications. Also reads `DISCORD_WEBHOOK_URL` env var.\n\nPer-agent fields:\n- `session_id` — UUID passed to `claude --session-id` at spawn; use with `claude --resume UUID` to restore exact session context after a crash or window close.\n- `last_rebriefed_at` — Unix timestamp of last re-brief; enforces 5-min cooldown to prevent spam.\n\nAgent states: `running` | `idle` | `stuck` | `waiting_approval` | `complete` | `done` | `escalated`\n\n`done` means verified complete — window is still open, session still alive, worktree still on task branch. Not recycled yet.\n\n## Serial /pr-test rule\n\n`/pr-test` and `/pr-test --fix` run local Docker + integration tests that use shared ports, a shared database, and shared build caches. **Running two `/pr-test` jobs simultaneously will cause port conflicts and database corruption.**\n\n**Rule: only one `/pr-test` runs at a time. The orchestrator serializes them.**\n\nYou (the orchestrating Claude) own the test queue:\n1. Agents do `pr-review` and `pr-address` in parallel — that's safe (they only push code and reply to GitHub).\n2. When a PR needs local testing, add it to your mental queue — don't give agents a `pr-test` step.\n3. Run `/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER --fix` yourself, sequentially.\n4. Feed results back to the relevant agent via `tmux send-keys`:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"Local tests for PR #N: <paste failure output or 'all passed'>. Fix any failures and push, then output ORCHESTRATOR:DONE.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n5. Wait for CI to confirm green before marking the agent done.\n\nIf multiple PRs need testing at the same time, pick the one furthest along (fewest pending CI checks) and test it first. Only start the next test after the previous one completes.\n\n## Session restore (tested and confirmed)\n\nAgent sessions are saved to disk. To restore a closed or crashed session:\n\n```bash\n# If session_id is in state (preferred):\nNEW_WIN=$(tmux new-window -t SESSION -n WORKTREE_NAME -P -F '#{window_index}')\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --resume SESSION_ID --permission-mode bypassPermissions\" Enter\n\n# If no session_id (use --continue for most recent session in that directory):\ntmux send-keys -t \"SESSION:${NEW_WIN}\" \"cd /path/to/worktree && claude --continue --permission-mode bypassPermissions\" Enter\n```\n\n`--continue` restores the full conversation history including all tool calls, file edits, and context. The agent resumes exactly where it left off. After restoring, update the window address in the state file:\n\n```bash\njq --arg old \"SESSION:OLD_WIN\" --arg new \"SESSION:NEW_WIN\" \\\n  '(.agents[] | select(.window == $old)).window = $new' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## Intent → action mapping\n\nMatch the user's message to one of these intents:\n\n| The user says something like… | What to do |\n|---|---|\n| \"status\", \"what's running\", \"show agents\" | Run `status.sh` + `capacity.sh`, show output |\n| \"how many free\", \"capacity\", \"available worktrees\" | Run `capacity.sh`, show output |\n| \"start N agents on X, Y, Z\" or \"run these tasks: …\" | See **Spawning agents** below |\n| \"add task: …\", \"add one more agent for …\" | See **Adding an agent** below |\n| \"stop\", \"shut down\", \"pause the fleet\" | See **Stopping** below |\n| \"poll\", \"check now\", \"run a cycle\" | Run `poll-cycle.sh`, process actions |\n| \"recycle window X\", \"free up autogpt3\" | Run `recycle-agent.sh` directly |\n\nWhen the intent is ambiguous, show capacity first and ask what tasks to run.\n\n## Spawning agents\n\n### 1. Resolve tmux session\n\n```bash\ntmux list-sessions -F \"#{session_name}: #{session_windows} windows\" 2>/dev/null\n```\n\nUse an existing session. **Never create a tmux session from within Claude** — it becomes a child of Claude's process and dies when the session ends. If no session exists, tell the user to run `tmux new-session -d -s autogpt1` in their terminal first, then re-invoke `/orchestrate`.\n\n### 2. Show available capacity\n\n```bash\nbash $SKILLS_DIR/capacity.sh $(git rev-parse --show-toplevel)\n```\n\n### 3. Collect tasks from the user\n\nFor each task, gather:\n- **objective** — what to do (e.g. \"implement feature X and open a PR\")\n- **branch name** — e.g. `feat/my-feature` (derive from objective if not given)\n- **pr_number** — GitHub PR number if working on an existing PR (for verification)\n- **steps** — required checkpoint names in order (e.g. `pr-address pr-test`) — derive from objective\n\nAsk for `idle_threshold_seconds` only if the user mentions it (default: 300).\n\nNever ask the user to specify a worktree — auto-assign from `find-spare.sh`.\n\n### 4. Spawn one agent per task\n\n```bash\n# Get ordered list of spare worktrees\nSPARE_LIST=$(bash $SKILLS_DIR/find-spare.sh $(git rev-parse --show-toplevel))\n\n# For each task, take the next spare line:\nWORKTREE_PATH=$(echo \"$SPARE_LINE\" | awk '{print $1}')\nSPARE_BRANCH=$(echo \"$SPARE_LINE\" | awk '{print $2}')\n\n# With PR number and required steps:\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\" \"$PR_NUMBER\" \"pr-address\" \"pr-test\")\n\n# Without PR (new work):\nWINDOW=$(bash $SKILLS_DIR/spawn-agent.sh \"$SESSION\" \"$WORKTREE_PATH\" \"$SPARE_BRANCH\" \"$NEW_BRANCH\" \"$OBJECTIVE\")\n```\n\nBuild an agent record and append it to the state file. If the state file doesn't exist yet, initialize it:\n\n```bash\n# Derive repo from git remote (used by verify-complete.sh + supervisor)\nREPO=$(git remote get-url origin 2>/dev/null | sed 's|.*github\\.com[:/]||; s|\\.git$||' || echo \"\")\n\njq -n \\\n  --arg session \"$SESSION\" \\\n  --arg repo \"$REPO\" \\\n  --argjson threshold 300 \\\n  '{active:true, tmux_session:$session, idle_threshold_seconds:$threshold,\n    repo:$repo, loop_window:null, supervisor_window:null, last_poll_at:0, agents:[]}' \\\n  > ~/.claude/orchestrator-state.json\n```\n\nOptionally add a Discord webhook for completion notifications:\n```bash\njq --arg hook \"$DISCORD_WEBHOOK_URL\" '.discord_webhook = $hook' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n`spawn-agent.sh` writes the initial agent record (window, worktree_path, branch, objective, state, etc.) to the state file automatically — **do not append the record again after calling it.** The record already exists and `pr_number`/`steps` are patched in by the script itself.\n\n### 5. Start the mechanical babysitter\n\n```bash\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\n\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n### 6. Begin supervising directly in this conversation\n\nYou are the supervisor. After spawning, immediately start your first poll loop (see **Supervisor duties** below) and continue every 2-3 minutes. Do NOT spawn a separate supervisor Claude window.\n\n## Adding an agent\n\nFind the next spare worktree, then spawn and append to state — same as steps 2–4 above but for a single task. If no spare worktrees are available, tell the user.\n\n## Supervisor duties (YOUR job, every 2-3 min in this conversation)\n\nYou are the supervisor. Run this poll loop directly in your Claude session — not in a separate window.\n\n### Poll loop mechanism\n\nYou are reactive — you only act when a tool completes or the user sends a message. To create a self-sustaining poll loop without user involvement:\n\n1. Start each poll with `run_in_background: true` + a sleep before the work:\n   ```bash\n   sleep 120 && tmux capture-pane -t autogpt1:0 -p -S -200 | tail -40\n   # + similar for each active window\n   ```\n2. When the background job notifies you, read the pane output and take action.\n3. Immediately schedule the next background poll — this keeps the loop alive.\n4. Stop scheduling when all agents are done/escalated.\n\n**Never tell the user \"I'll poll every 2-3 minutes\"** — that does nothing without a trigger. Start the background job instead.\n\n### Each poll: what to check\n\n```bash\n# 1. Read state\ncat ~/.claude/orchestrator-state.json | jq '.agents[] | {window, worktree, branch, state, pr_number, checkpoints}'\n\n# 2. For each running/stuck/idle agent, capture pane\ntmux capture-pane -t SESSION:WIN -p -S -200 | tail -60\n```\n\nFor each agent, decide:\n\n| What you see | Action |\n|---|---|\n| Spinner / tools running | Do nothing — agent is working |\n| Idle `❯` prompt, no `ORCHESTRATOR:DONE` | Stalled — send specific nudge with objective from state |\n| Stuck in error loop | Send targeted fix with exact error + solution |\n| Waiting for input / question | Answer and unblock via `tmux send-keys` |\n| CI red | `gh pr checks PR_NUMBER --repo REPO` → tell agent exactly what's failing |\n| GitHub abuse rate limit error | Nudge: \"Wait 60 seconds then continue posting replies with sleep 3 between each\" |\n| Context compacted / agent lost | Send recovery: `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"WIN\")'` + `gh pr view PR_NUMBER --json title,body` |\n| `ORCHESTRATOR:DONE` in output | Query GraphQL for actual unresolved count. If >0, re-brief. If 0, run `verify-complete.sh` |\n\n**Poll all windows from state, not from memory.** Before each poll, run:\n```bash\njq -r '.agents[] | select(.state | test(\"running|idle|stuck|waiting_approval|pending_evaluation\")) | .window' ~/.claude/orchestrator-state.json\n```\nand capture every window listed. If you manually added a window outside spawn-agent.sh, ensure it's in the state file first.\n\n### RUNNING count includes waiting_approval agents\n\nThe `RUNNING` count from run-loop.sh includes agents in `waiting_approval` state (they match the regex `running|stuck|waiting_approval|idle`). This means a fleet that is only `waiting_approval` still shows RUNNING > 0 in the log — it does **not** mean agents are actively working.\n\nWhen you see `RUNNING > 0` in the run-loop log but suspect agents are actually blocked, check state directly:\n```bash\njq '.agents[] | {window, state, worktree}' ~/.claude/orchestrator-state.json\n```\nA count of `running=1 waiting=1` in the log actually means one agent is waiting for approval — the orchestrator should check and approve, not wait.\n\n### State file staleness recovery\n\nThe state file is written by scripts but can drift from reality when windows are closed, sessions expire, or the orchestrator restarts across conversations.\n\n**Signs of stale state:**\n- `loop_window` points to a window that no longer exists in the tmux session\n- An agent's `state` is `running` but tmux window is closed or shows a shell prompt (not claude)\n- `last_seen_at` is hours old but state still says `running`\n\n**Recovery steps:**\n\n1. **Verify actual tmux windows:**\n```bash\ntmux list-windows -t SESSION -F '#{window_index}: #{window_name} (#{pane_current_command})'\n```\n\n2. **Cross-reference with state file:**\n```bash\njq -r '.agents[] | \"\\(.window) \\(.state) \\(.worktree)\"' ~/.claude/orchestrator-state.json\n```\n\n3. **Fix stale entries:**\n```bash\n# Agent window closed — mark idle so run-loop.sh will restart it\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window==$w)).state = \"idle\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\n# loop_window gone — kill the stale reference, then restart run-loop.sh\njq '.loop_window = null' ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\nLOOP_WIN=$(tmux new-window -t \"$SESSION\" -n \"orchestrator\" -P -F '#{window_index}')\nLOOP_WINDOW=\"${SESSION}:${LOOP_WIN}\"\ntmux send-keys -t \"$LOOP_WINDOW\" \"bash $SKILLS_DIR/run-loop.sh\" Enter\njq --arg w \"$LOOP_WINDOW\" '.loop_window = $w' ~/.claude/orchestrator-state.json \\\n  > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n4. **After any state repair, re-run `status.sh` to confirm coherence before resuming supervision.**\n\n### Strict ORCHESTRATOR:DONE gate\n\n`verify-complete.sh` handles the main checks automatically (checkpoints, threads, CI green, spawned_at, and CHANGES_REQUESTED). Run it:\n\n**CHANGES_REQUESTED staleness rule**: a `CHANGES_REQUESTED` review only blocks if it was submitted *after* the latest commit. If the latest commit postdates the review, the review is considered stale (feedback already addressed) and does not block. This avoids false negatives when a bot reviewer hasn't re-reviewed after the agent's fixing commits.\n\n```bash\nSKILLS_DIR=~/.claude/orchestrator/scripts\nbash $SKILLS_DIR/verify-complete.sh SESSION:WIN\n```\n\nIf it passes → run-loop.sh will recycle the window automatically. No manual action needed.\nIf it fails → re-brief the agent with the failure reason. Never manually mark state `done` to bypass this.\n\n### Re-brief a stalled agent\n\n**Before sending any nudge, verify the pane is at an idle ❯ prompt.** Sending text into a still-processing pane produces stuck `[Pasted text +N lines]` that the agent never sees.\n\nCheck:\n```bash\ntmux capture-pane -t SESSION:WIN -p 2>/dev/null | tail -5\n```\nIf the last line shows a spinner (✳✽✢✶·), `Running…`, or no `❯` — wait 10–15s and check again before sending.\n\n```bash\nOBJ=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .objective' ~/.claude/orchestrator-state.json)\nPR=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .pr_number' ~/.claude/orchestrator-state.json)\ntmux send-keys -t SESSION:WIN \"You appear stalled. Your objective: $OBJ. Check: gh pr view $PR --json title,body,headRefName to reorient.\"\nsleep 0.3\ntmux send-keys -t SESSION:WIN Enter\n```\n\nIf `image_path` is set on the agent record, include: \"Re-read context at IMAGE_PATH with the Read tool.\"\n\n## Self-recovery protocol (agents)\n\nspawn-agent.sh automatically includes this instruction in every objective:\n\n> If your context compacts and you lose track of what to do, run:\n> `cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window==\"SESSION:WIN\")'`\n> and `gh pr view PR_NUMBER --json title,body,headRefName` to reorient.\n> Output each completed step as `CHECKPOINT:<step-name>` on its own line.\n\n## Passing images and screenshots to agents\n\n`tmux send-keys` is text-only — you cannot paste a raw image into a pane. To give an agent visual context (screenshots, diagrams, mockups):\n\n1. **Save the image to a temp file** with a stable path:\n   ```bash\n   # If the user drags in a screenshot or you receive a file path:\n   IMAGE_PATH=\"/tmp/orchestrator-context-$(date +%s).png\"\n   cp \"$USER_PROVIDED_PATH\" \"$IMAGE_PATH\"\n   ```\n\n2. **Reference the path in the objective string**:\n   ```bash\n   OBJECTIVE=\"Implement the layout shown in /tmp/orchestrator-context-1234567890.png. Read that image first with the Read tool to understand the design.\"\n   ```\n\n3. The agent uses its `Read` tool to view the image at startup — Claude Code agents are multimodal and can read image files directly.\n\n**Rule**: always use `/tmp/orchestrator-context-<timestamp>.png` as the naming convention so the supervisor knows what to look for if it needs to re-brief an agent with the same image.\n\n---\n\n## Orchestrator final evaluation (YOU decide, not the script)\n\n`verify-complete.sh` is a gate — it blocks premature marking. But it cannot tell you if the work is actually good. That is YOUR job.\n\nWhen run-loop marks an agent `pending_evaluation` and you're notified, do all of these before marking done:\n\n### 1. Run /pr-test (required, serialized, use TodoWrite to queue)\n\n`/pr-test` is the only reliable confirmation that the objective is actually met. Run it yourself, not the agent.\n\n**When multiple PRs reach `pending_evaluation` at the same time, use TodoWrite to queue them:**\n```\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/NNNN — <feature description>\n- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/MMMM — <feature description>\n```\nRun one at a time. Check off as you go.\n\n```\n/pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/PR_NUMBER\n```\n\n**/pr-test can be lazy** — if it gives vague output, re-run with full context:\n\n```\n/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER\nContext: This PR implements <objective from state file>. Key files: <list>.\nPlease verify: <specific behaviors to check>.\n```\n\nOnly one `/pr-test` at a time — they share ports and DB.\n\n### /pr-test result evaluation\n\n**PARTIAL on any headline feature scenario is an immediate blocker.** Do not approve, do not mark done, do not let the agent output `ORCHESTRATOR:DONE`.\n\n| `/pr-test` result | Action |\n|---|---|\n| All headline scenarios **PASS** | Proceed to evaluation step 2 |\n| Any headline scenario **PARTIAL** | Re-brief the agent immediately — see below |\n| Any headline scenario **FAIL** | Re-brief the agent immediately |\n\n**What PARTIAL means**: the feature is only partly working. Example: the Apply button never appeared, or the AI returned no action blocks. The agent addressed part of the objective but not all of it.\n\n**When any headline scenario is PARTIAL or FAIL:**\n\n1. Do NOT mark the agent done or accept `ORCHESTRATOR:DONE`\n2. Re-brief the agent with the specific scenario that failed and what was missing:\n   ```bash\n   tmux send-keys -t SESSION:WIN \"PARTIAL result on /pr-test — S5 (Apply button) never appeared. The AI must output JSON action blocks for the Apply button to render. Fix this before re-running /pr-test.\"\n   sleep 0.3\n   tmux send-keys -t SESSION:WIN Enter\n   ```\n3. Set state back to `running`:\n   ```bash\n   jq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"running\"' \\\n     ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n   ```\n4. Wait for new `ORCHESTRATOR:DONE`, then re-run `/pr-test` from scratch\n\n**Rule: only ALL-PASS qualifies for approval.** A mix of PASS + PARTIAL is a failure.\n\n> **Why this matters**: A PR was once wrongly approved with S5 PARTIAL — the AI never output JSON action blocks so the Apply button never appeared. The fix was already in the agent's reach but slipped through because PARTIAL was not treated as blocking.\n\n### 2. Do your own evaluation\n\n1. **Read the PR diff and objective** — does the code actually implement what was asked? Is anything obviously missing or half-done?\n2. **Read the resolved threads** — were comments addressed with real fixes, or just dismissed/resolved without changes?\n3. **Check CI run names** — any suspicious retries that shouldn't have passed?\n4. **Check the PR description** — title, summary, test plan complete?\n\n### 3. Decide\n\n- `/pr-test` all scenarios PASS + evaluation looks good → mark `done` in state, tell the user the PR is ready, ask if window should be closed\n- `/pr-test` any scenario PARTIAL or FAIL → re-brief the agent with the specific failing scenario, set state back to `running` (see `/pr-test result evaluation` above)\n- Evaluation finds gaps even with all PASS → re-brief the agent with specific gaps, set state back to `running`\n\n**Never mark done based purely on script output.** You hold the full objective context; the script does not.\n\n```bash\n# Mark done after your positive evaluation:\njq --arg w \"SESSION:WIN\" '(.agents[] | select(.window == $w)).state = \"done\"' \\\n  ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n```\n\n## When to stop the fleet\n\nStop the fleet (`active = false`) when **all** of the following are true:\n\n| Check | How to verify |\n|---|---|\n| All agents are `done` or `escalated` | `jq '[.agents[] | select(.state | test(\"running\\|stuck\\|idle\\|waiting_approval\"))] | length' ~/.claude/orchestrator-state.json` == 0 |\n| All PRs have 0 unresolved review threads | GraphQL `isResolved` check per PR |\n| All PRs have green CI **on a run triggered after the agent's last push** | `gh run list --branch BRANCH --limit 1` timestamp > `spawned_at` in state |\n| No fresh CHANGES_REQUESTED (after latest commit) | `verify-complete.sh` checks this — stale pre-commit reviews are ignored |\n| No agents are `escalated` without human review | If any are escalated, surface to user first |\n\n**Do NOT stop just because agents output `ORCHESTRATOR:DONE`.** That is a signal to verify, not a signal to stop.\n\n**Do stop** if the user explicitly says \"stop\", \"shut down\", or \"kill everything\", even with agents still running.\n\n```bash\n# Graceful stop\njq '.active = false' ~/.claude/orchestrator-state.json > /tmp/orch.tmp \\\n  && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json\n\nLOOP_WINDOW=$(jq -r '.loop_window // \"\"' ~/.claude/orchestrator-state.json)\n[ -n \"$LOOP_WINDOW\" ] && tmux kill-window -t \"$LOOP_WINDOW\" 2>/dev/null || true\n```\n\nDoes **not** recycle running worktrees — agents may still be mid-task. Run `capacity.sh` to see what's still in progress.\n\n## tmux send-keys pattern\n\n**Always split long messages into text + Enter as two separate calls with a sleep between them.** If sent as one call (`\"text\" Enter`), Enter can fire before the full string is buffered into Claude's input — leaving the message stuck as `[Pasted text +N lines]` unsent.\n\n```bash\n# CORRECT — text then Enter separately\ntmux send-keys -t \"$WINDOW\" \"your long message here\"\nsleep 0.3\ntmux send-keys -t \"$WINDOW\" Enter\n\n# WRONG — Enter may fire before text is buffered\ntmux send-keys -t \"$WINDOW\" \"your long message here\" Enter\n```\n\nShort single-character sends (`y`, `Down`, empty Enter for dialog approval) are safe to combine since they have no buffering lag.\n\n---\n\n## Protected worktrees\n\nSome worktrees must **never** be used as spare worktrees for agent tasks because they host files critical to the orchestrator itself:\n\n| Worktree | Protected branch | Why |\n|---|---|---|\n| `AutoGPT1` | `dx/orchestrate-skill` | Hosts the orchestrate skill scripts. `recycle-agent.sh` would check out `spare/1`, wiping `.claude/skills/` and breaking all subsequent `spawn-agent.sh` calls. |\n\n**Rule**: when selecting spare worktrees via `find-spare.sh`, skip any worktree whose CURRENT branch matches a protected branch. If you accidentally spawn an agent in a protected worktree, do not let `recycle-agent.sh` run on it — manually restore the branch after the agent finishes.\n\nWhen `dx/orchestrate-skill` is merged into `dev`, `AutoGPT1` becomes a normal spare again.\n\n---\n\n## Thread resolution integrity (critical)\n\n**Agents MUST NOT resolve review threads via GraphQL unless a real code fix has been committed and pushed first.**\n\nThis is the most common failure mode: agents call `resolveReviewThread` to make unresolved counts drop without actually fixing anything. This produces a false \"done\" signal that gets past verify-complete.sh.\n\n**The only valid resolution sequence:**\n1. Read the thread and understand what it's asking\n2. Make the actual code change\n3. `git commit` and `git push`\n4. Reply to the thread with the commit SHA (e.g. \"Fixed in `abc1234`\")\n5. THEN call `resolveReviewThread`\n\n**The supervisor must verify actual thread counts via GraphQL** — never trust an agent's claim of \"0 unresolved.\" After any agent's ORCHESTRATOR:DONE, always run:\n\n```bash\n# Step 1: get total count\nTOTAL=$(gh api graphql -f query='{ repository(owner: \"OWNER\", name: \"REPO\") { pullRequest(number: PR) { reviewThreads { totalCount } } } }' \\\n  | jq '.data.repository.pullRequest.reviewThreads.totalCount')\necho \"Total threads: $TOTAL\"\n\n# Step 2: paginate all pages and count unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"{ repository(owner: \\\"OWNER\\\", name: \\\"REPO\\\") { pullRequest(number: PR) { reviewThreads(first: 100${AFTER}) { pageInfo { hasNextPage endCursor } nodes { isResolved } } } } }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved: $UNRESOLVED\"\n```\n\nIf unresolved > 0, the agent is NOT done — re-brief with the actual count and the rule.\n\n**Include this in every agent objective:**\n> IMPORTANT: Do NOT resolve any review thread via GraphQL unless the code fix is committed and pushed first. Fix the code → commit → push → reply with SHA → then resolve. Never resolve without a real commit. \"Accepted\" or \"Acknowledged\" replies are NOT resolutions — only real commits qualify.\n\n### Detecting fake resolutions\n\nWhen an agent claims \"0 unresolved threads\", query GitHub GraphQL yourself and also inspect how each thread was resolved. A resolved thread whose last comment is `\"Acknowledged\"`, `\"Same as above\"`, `\"Accepted trade-off\"`, or `\"Deferred\"` — with no commit SHA — is a fake resolution.\n\nTo spot these, paginate all pages and collect resolved threads with missing SHA links:\n```bash\n# Paginate all pages — first:100 misses threads beyond page 1 on large PRs\nCURSOR=\"\"; FAKE_RESOLUTIONS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: PR_NUMBER) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            isResolved\n            comments(last: 1) {\n              nodes { body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  PAGE_FAKES=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[]\n    | select(.isResolved == true)\n    | {body: .comments.nodes[0].body[:120], author: .comments.nodes[0].author.login}\n    | select(.body | test(\"Fixed in|Removed in|Addressed in\") | not)]')\n  FAKE_RESOLUTIONS=$(echo \"$FAKE_RESOLUTIONS $PAGE_FAKES\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"$FAKE_RESOLUTIONS\"\n```\nAny resolved thread whose last comment does NOT contain `\"Fixed in\"`, `\"Removed in\"`, or `\"Addressed in\"` (with a commit link) should be investigated — either the agent falsely resolved it, or it was a genuine false positive that needs explanation.\n\n## GitHub abuse rate limits\n\nTwo distinct rate limits exist with different recovery times:\n\n| Error | HTTP status | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` in body | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `API rate limit exceeded` | 429 | Primary rate limit — too many read calls per hour | Wait until `X-RateLimit-Reset` timestamp |\n\n**Prevention:** Agents must add `sleep 3` between individual thread reply API calls. For >20 unresolved threads, increase to `sleep 5`.\n\nIf you see a 403 `abuse` error from an agent's pane:\n1. Nudge the agent: `\"You hit a GitHub secondary rate limit (403). Stop all API writes. Wait 2 minutes, then resume with sleep 3 between each thread reply.\"`\n2. Do NOT nudge again during the 2-minute wait — a second nudge restarts the clock.\n\nAdd this to agent briefings when there are >20 unresolved threads:\n> Post replies with `sleep 3` between each reply. If you hit a 403 abuse error, wait 2 minutes (not 60s — secondary limits take longer to clear) then continue.\n\n## Key rules\n\n1. **Scripts do all the heavy lifting** — don't reimplement their logic inline in this file\n2. **Never ask the user to pick a worktree** — auto-assign from `find-spare.sh` output\n3. **Never restart a running agent** — only restart on `idle` kicks (foreground is a shell)\n4. **Auto-dismiss settings dialogs** — if \"Enter to confirm\" appears, send Down+Enter\n5. **Always `--permission-mode bypassPermissions`** on every spawn\n6. **Escalate after 3 kicks** — mark `escalated`, surface to user\n7. **Atomic state writes** — always write to `.tmp` then `mv`\n8. **Never approve destructive commands** outside the worktree scope — when in doubt, escalate\n9. **Never recycle without verification** — `verify-complete.sh` must pass before recycling\n10. **No TASK.md files** — commit risk; use state file + `gh pr view` for agent context persistence\n11. **Re-brief stalled agents** — read objective from state file + `gh pr view`, send via tmux\n12. **ORCHESTRATOR:DONE is a signal to verify, not to accept** — always run `verify-complete.sh` and check CI run timestamp before recycling\n13. **Protected worktrees** — never use the worktree hosting the skill scripts as a spare\n14. **Images via file path** — save screenshots to `/tmp/orchestrator-context-<ts>.png`, pass path in objective; agents read with the `Read` tool\n15. **Split send-keys** — always separate text and Enter with `sleep 0.3` between calls for long strings\n16. **Poll ALL windows from state file** — never hardcode window count. Derive active windows dynamically: `jq -r '.agents[] | select(.state | test(\"running|idle|stuck\")) | .window' ~/.claude/orchestrator-state.json`. If you added a window mid-session outside spawn-agent.sh, add it to the state file immediately.\n20. **Orchestrator handles its own approvals** — when spawning a subagent to make edits (SKILL.md, scripts, config), review the diff yourself and approve/reject without surfacing it to the user. The user should never have to open a file to check the orchestrator's work. Use the Agent tool with `subagent_type: general-purpose` for drafting, then verify the result yourself before considering the task done.\n17. **Update state file on re-task** — whenever an agent is re-tasked mid-session (objective changes, new PR assigned), update the state file record immediately so objectives stay accurate for re-briefing after compaction.\n18. **No GraphQL resolveReviewThread without a commit** — see Thread resolution integrity above. This is rule #1 for pr-address work.\n19. **Verify thread counts yourself** — after any agent claims \"0 unresolved threads\", query GitHub GraphQL directly before accepting. Never trust the agent's self-report.\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_baseline.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/aggregate_candidate.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/error_400.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/error_400.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/error_400.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/project_builder.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/project_builder.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/project_builder.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page1.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_baseline_page2.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page1.json)\n\n<!-- File: .claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Fixtures Skill\n\nPath: `.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/perf-compare/scripts/fixtures/samples_candidate_page2.json)\n\n<!-- File: .claude/skills/perf-compare/SKILL.md (Tokens: ~5093 | Category: anthropic-skill) -->\n---\nname: perf-compare\ndescription: \"Shared measurement recipe for AutoGPT performance work. Compares a baseline and a candidate in Sentry (two releases, two environments such as preview pr-<n> deploys, or two time windows) per transaction: count, p50/p95/p99, failure rate, and a trace-clustered bootstrap verdict of faster / slower / no detectable change / not enough data, with exit codes agents can branch on. Works for span duration, web vitals (measurements.lcp) or any numeric span field. TRIGGER when an agent needs to show a change made the backend or frontend faster or slower, compare a preview against a control or dev, check a release after merge on dev or prod, or check for regressions.\"\nuser-invocable: true\nargument-hint: \"[what to compare, e.g. 'builder release OLD vs NEW on prod' or 'pr-123 vs control pr-124 on /api/library/agents']\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# perf-compare: one way to measure performance\n\nEvery agent that tunes speed or stability measures with this recipe, so results are comparable across agents and noise is not reported as a win. Sentry is the store: org `significant-gravitas`, API `https://us.sentry.io`, tracing at 100%. `scripts/perf_compare.py` (Python 3.8+, standard library only) runs the queries and the statistics.\n\n```bash\nS=$(git rev-parse --show-toplevel)/.claude/skills/perf-compare/scripts/perf_compare.py\npython3 \"$S\" --help\n```\n\n## When to use it\n\n- Before claiming a change made something faster or slower, on a preview or after merge.\n- After merge, to check a win holds on dev and then prod (release against release).\n- To check a change did not regress other transactions (run without `--transaction`).\n- For any numeric field: `span.duration` (default), web vitals on builder pageloads (`measurements.lcp`), LLM token counts (`gen_ai.usage.input_tokens`), or custom attributes (`tags[name,number]`).\n\nIt is not for debugging one slow request (open the trace) or for error triage.\n\n## Token\n\nThe script reads `SENTRY_AUTH_TOKEN` from the environment and nowhere else. It needs a read-only token with exactly `org:read`, `project:read` and `event:read`: either a user auth token (Settings > Account > API > Auth Tokens) or an internal integration with Read on Organization, Project and Issue & Event. Organization auth tokens (`sntrys_...`) carry only `org:ci` and get HTTP 403 on these endpoints. A missing token exits with code 5 and these instructions. `--print-queries` needs no token.\n\n## Where things are in Sentry\n\n| Project | What it holds | Environments | `release` |\n|---|---|---|---|\n| `autogpt-server` | Backend: FastAPI routes (`/api/library/agents`, `/api/chat/sessions/{session_id}/stream`) and internal RPC services (`/update_node_execution_status`) | `app:prod-behave:cloud`, `app:dev-behave:cloud` | git sha once the backend sets it; check first |\n| `builder` | Frontend (Next.js on Vercel): `http.server` routes (`GET /api/proxy/[...path]`), `middleware GET`, `serverAction/*`, browser `pageload` and `navigation` with web vitals | `app:prod-behave:cloud` is prod; `app:preview-behave:cloud` is dev **and** every Vercel preview | git sha |\n| `autogpt-server-previews`, `builder-previews` | `!deploy` previews | `pr-<n>` | git sha |\n\nCheck a selector has data before you measure:\n\n```text\nsearch_events  projectSlug=<project>  dataset=spans  query=\"is_transaction:true\"\n               fields=[\"environment\",\"release\",\"count()\"]  sort=\"-count()\"  period=\"7d\"\n```\n\nIf `release` comes back null for a project, select by time window instead of release. If `autogpt-server-previews` has no `pr-<n>` events, that preview's backend is reporting into `autogpt-server` as `app:dev-behave:cloud`, mixed with dev traffic, and cannot be measured separately.\n\n## Choosing baseline and candidate\n\nBoth sides take `KEY=VALUE` pairs: `project=`, `env=`, `release=`, `query=` (extra Sentry filter), and time as `window=START/END` (repeatable), `start=`/`end=`, or `period=24h`. Shared defaults come from `--project`, `--env`, `--query` and `--period`/`--start`/`--end`/`--window`. A side's own `project=`, `env=` or `query=` wins over the shared value (`env=` with nothing after it clears it). Any time key on a side (`window=`, `start=`/`end=` or `period=`) replaces all of the shared time flags for that side. Times are UTC ISO 8601; without a time anywhere, both sides use the last 7 days.\n\n**1. After merge: release against release (dev or prod).** Same project and environment, previous release against new release, in windows of equal length that cover the same hours of the day: the 24 hours before the deploy against the 24 hours after, or 7 days against 7 days for quiet routes. Always filter by release when there is one. Old releases keep sending events for days (open tabs, Vercel skew protection), so a time window alone mixes releases.\n\n```bash\npython3 \"$S\" --project builder --env app:prod-behave:cloud \\\n  --baseline release=OLD_SHA window=2026-10-10T14:00:00Z/2026-10-11T14:00:00Z \\\n  --candidate release=NEW_SHA window=2026-10-11T14:00:00Z/2026-10-12T14:00:00Z\n```\n\n**2. On a preview: candidate preview against a control preview.** Deploy the change with `!deploy`, and deploy a control (the dev commit the change branches from, with no changes) the same way. Each gets its own namespace and an empty branch database, so the two are alike. Drive the same load at both, either together in one window or interleaved A B B A (control, candidate, candidate, control; about 10 minutes each) so drift cancels:\n\n```bash\npython3 \"$S\" --project autogpt-server-previews --transaction '/api/store/agents' \\\n  --baseline env=pr-CONTROL window=T0/T1 window=T3/T4 \\\n  --candidate env=pr-CANDIDATE window=T1/T2 window=T2/T3\n```\n\nMeasuring the candidate preview against `app:dev-behave:cloud` at dev HEAD in the same window is cheaper but weaker: dev has a full database, real traffic and different pods. Use it as a rough read, not as proof.\n\n**3. No release tag: before against after.** Equal windows either side of the deploy. Other deploys inside those windows also change things, so say so in your report.\n\n**4. A/A check before trusting a win.** Compare a selector with itself over two windows, for example the same release yesterday and today. It should say `no detectable change`. If it does not, your windows differ too much (hour of day, weekday, traffic mix), and so will your real comparison.\n\n## How much data\n\n- The script gives no p50 or p95 verdict unless each side has `--min-count` samples (default 200) **from** `--min-traces` distinct traces (default 30). At 200, p95 rests on 10 values above it. p99 is shown but never judged. The verdict cell says which minimum was missed. The failure-rate test works from Sentry's event counts instead, and needs `--min-count` events per side.\n- It samples up to `--sample-cap` values per side per transaction (default 1000, at most 20000), paging 100 at a time. A cap below `--min-count` is refused.\n- For a claim about one transaction, name it with `--transaction` and raise `--sample-cap` to 2000–5000. Naming it also shrinks the multiple-comparison correction (see below).\n- Correlated spans count once: the bootstrap resamples whole traces, and the trace minimum stops a few busy traces (a page polling an endpoint) from passing as a big sample. The table shows samples and distinct traces. The effective sample is closer to the trace count than the span count. In A/A tests on clustered data, resampling spans one at a time called 38–44% of identical pairs a change, against 2–5% when resampling by trace.\n- Drive at least 1000 requests per side per transaction you care about. Wait a few minutes after load stops before measuring, so Sentry has the last events.\n\n## Driving load on a preview\n\nA preview's backend is `https://pr-<N>-server.agpt.co` (REST under `/api`, health at `/health`), and its frontend is `https://autogpt-pr-<N>.vercel.app`.\n\nThe k6 tests are in `autogpt_platform/backend/load-tests/`; its `README.md` covers every test and setting. Run them on your own machine: `./k6.sh` runs k6 in Docker (`grafana/k6`), so nothing else needs installing, and a local run costs nothing beyond the load on the target. Do not run them on Grafana Cloud k6. It bills every virtual-user hour, so `./k6.sh` refuses cloud runs unless they are explicitly opted into, and the README reserves them for rare runs the team has agreed to. `K6_ENVIRONMENT=PREVIEW` with `PR_NUMBER=<N>` targets a preview:\n\n```bash\ncd autogpt_platform/backend/load-tests\nexport K6_ENVIRONMENT=PREVIEW PR_NUMBER=<N>\nVUS=5 DURATION=10m RAMP_UP=30s RAMP_DOWN=30s ./k6.sh run tests/api/core-api-test.js\n```\n\n- **Authentication needs no setup on a preview.** The authenticated tests (`tests/api/*`, `tests/basic/*`, `tests/marketplace/library-access-test.js`, `tests/comprehensive/platform-journey-test.js`) sign up throwaway `loadtest-*@previews.agpt.co` accounts in `setup()`, once per run, and use the Better Auth tokens the preview's frontend issues for them. `LOAD_TEST_ACCOUNT_COUNT` sets how many (default 5). `tests/marketplace/public-access-test.js` needs no account at all.\n- **To run as specific accounts**, such as the seeded `preview-*@previews.agpt.co` personas, set `LOAD_TEST_ACCOUNTS` to a JSON array of `{\"email\", \"password\"}`. The tests sign each one in once per run. Dev has no throwaway sign-up, so it always needs `LOAD_TEST_ACCOUNTS`.\n- **Note the run's UTC window** (`date -u +%FT%TZ` before and after) for the baseline and candidate windows above, and drive the same test, VUs and duration at both sides.\n- **Previews start with an empty database.** Store and library lists return nothing, so costs that grow with data (N+1 queries, scans) do not show. Seed data through the API first, or confirm on dev and prod after merge.\n- None of the k6 tests open copilot chats. To measure copilot turns, run chats against the preview yourself, or measure on dev and prod traffic.\n- The repository's evals (`backend/copilot/eval`, `backend/copilot/capabilities/eval`, `poetry run expert-style-eval`, `poetry run brain-dump-eval`) are offline quality and cost harnesses. They send no traffic to a deploy.\n- After merge, real traffic on dev and prod is the load.\n\n## Reading the verdict\n\nThe markdown output (default) or `--json` reports two things for each transaction.\n\n- **Sentry aggregates over every matching event:** count, p50, p95, p99 and failure rate. These are exact for the windows.\n- **A verdict for p50 and p95:**\n  - The interval is a bootstrap CI of candidate minus baseline from the raw samples, resampling whole traces.\n  - It is Bonferroni-adjusted so that every interval in the run holds together at `--confidence` (95%). Two transactions means four intervals at 98.75% each. The plain 95% interval is in the JSON as `ci`.\n  - Resamples are raised automatically until each tail of the adjusted interval holds 20 draws, up to 50000. For example, 4000 becomes 8000 for five transactions. At the 50000 cap a run still goes ahead with as few as 10 draws per tail. A run that would leave fewer than 10, because too many transactions are compared, is refused before it starts (exit 2). The JSON reports the number used as `settings.resamples`.\n  - `faster` or `slower` needs the adjusted interval to exclude zero, and **both** the sample change and Sentry's all-events change to be at least `--min-effect` (5%) in that direction. Anything else is `no detectable change`. Below `--min-count` samples or `--min-traces` traces it is `not enough data`.\n  - The transaction takes its worst percentile: `slower` if either is slower, else `faster` if either is faster.\n- **Failure rate:** Fisher's exact test on the failure counts behind Sentry's `failure_rate()`. It is exact because a normal approximation is wrong with a handful of failures: 0 of 1000 against 4 of 1000 passes a z-test, but Fisher gives p = 0.12.\n  - The significance level is Bonferroni-adjusted over the K transactions that have `--min-count` events on both sides. That is one test per transaction, not two.\n  - `higher` needs significance and a rise of at least `--min-failure-delta` (0.1 percentage points). It counts as a regression even when the transaction got faster, because requests that fail fast look like a speedup.\n  - The test treats every event as independent, and `--min-traces` does not apply to it. When failures come from a few traces (one broken page retrying), it overstates significance. Check `sample_traces` and the failing traces in Sentry before acting on a failure-rate result alone.\n\n`no detectable change` means \"not distinguishable from noise with this much data\". It does not mean \"no change\". Check the interval widths and trace counts before concluding anything.\n\n**A win counts** only when all of these hold:\n\n- you named the transaction with `--transaction` before measuring;\n- the verdict is `faster`;\n- the failure rate is not `higher`;\n- it holds again on a second, independent window or run.\n\nA run over the top transactions is for spotting regressions and leads, not for claiming wins.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | faster: a compared transaction got faster and nothing regressed |\n| 1 | slower or more failures: at least one compared transaction regressed |\n| 2 | usage error: bad arguments (including a `--region-url` that is not https, `--timeout` ≤ 0, or `--sample-cap` below `--min-count`), unknown project, or Sentry rejected the query (HTTP 400) |\n| 3 | no detectable change |\n| 4 | not enough data: no compared transaction met `--min-count` and `--min-traces` on both sides |\n| 5 | auth: `SENTRY_AUTH_TOKEN` missing, or Sentry rejected it (401/403) |\n| 6 | Sentry API or network error after retries, or any unexpected error; never read as a regression |\n\nWith `--json`, the fields to branch on are:\n\n- `overall.verdict`, `overall.exit_code` and `overall.reasons`;\n- per transaction in `transactions[]`:\n  - `verdict`, `note` (why there is not enough data), `regression`;\n  - `p50`/`p95`: `baseline`, `candidate`, `diff`, `rel`, `sentry_rel`, `ci`, `ci_adjusted`, `verdict`;\n  - `failure_rate`: `baseline`, `candidate`, `diff`, `p_value`, `verdict`;\n  - `baseline`/`candidate`: `count`, `p50`, `p95`, `p99`, `failure_rate`, `sample_n`, `sample_traces`;\n- `settings`, holding the values actually used: `sample_cap`, `resamples`, `comparisons`, `failure_comparisons`, `adjusted_confidence`, `min_count`, `min_traces`, `min_effect`, `seed`.\n\nErrors in `--json` mode print `{\"error\": {\"type\", \"message\"}, \"exit_code\"}`, where `type` is `usage`, `auth`, `api` or `internal`.\n\n## The exact queries\n\nAll requests are `GET https://us.sentry.io/api/0/organizations/significant-gravitas/events/` with `dataset=spans` and `project=<numeric id>`. The id comes from `GET /api/0/projects/significant-gravitas/<slug>/`. `start` and `end` (UTC) span the side's windows; the query's timestamp filter pins each window exactly. `F` is `--field`.\n\n1. **Aggregate per transaction**, one request per side:\n   - `field=transaction`, `count()`, `p50(F)`, `p95(F)`, `p99(F)`, `failure_rate()`\n   - `sort=-count()`, `per_page=100`\n2. **Raw sample**, per side per compared transaction:\n   - `field=F`, `trace`, `id`\n   - `sort=id`, `per_page=100`, paged with the `cursor` from the `Link` header (`rel=\"next\"; results=\"true\"`) until `--sample-cap` rows.\n   - Span ids are random, so the first N rows by `id` are a uniform random sample of the windows. Sorting by `-timestamp` instead returns the latest burst, often a few traces.\n\nBoth use this query string:\n\n```text\nis_transaction:true environment:\"ENV\" release:\"SHA\" [has:F] [(EXTRA QUERY)]\n  [transaction:\"T\" | (transaction:\"A\" OR transaction:\"B\")]\n  timestamp:>=2026-10-11T14:00:00Z timestamp:<2026-10-12T14:00:00Z\n```\n\nSeveral windows become `(timestamp:>=S1 timestamp:<E1 OR timestamp:>=S2 timestamp:<E2)`. `has:F` appears for any field other than `span.duration`.\n\n`python3 \"$S\" ... --print-queries` prints the exact REST URL and MCP arguments for an invocation, without a token.\n\n**With only the Sentry MCP**, use `search_events` with `dataset: \"spans\"`, or `search_traces` without `dataset`:\n\n- Pass `projectSlug`, the same `query`, `fields` and `sort`.\n- Pass a `period` long enough to cover the windows, such as `\"50h\"`; the timestamp filter does the exact cut. The argument is `period`, not `statsPeriod`, and an unknown key silently falls back to 14 days.\n- Pass `limit` ≤ 100.\n- Confirm the \"Executed Search\" block in the reply shows your query unchanged.\n- The MCP cannot page. For more than 100 samples, split each window into sub-windows, get `count()` for each, and take the first rows by `id` from each in proportion to its count.\n\n## Field names checked against live data\n\nChecked through the Sentry MCP on `builder` and `autogpt-server`.\n\n- **Filters:**\n  - `is_transaction:true` and `transaction:\"…\"`.\n  - `environment:\"…\"`: quote it, the values contain colons.\n  - `release:\"<sha>\"`.\n  - `span.op:` with `pageload`, `navigation`, `http.server`, `http.server.middleware` or `function.server_action`.\n  - `span.status:[…]` and `!span.status:[…]`.\n  - `has:measurements.lcp` and `has:tags[gen_ai.usage.input_tokens,number]`.\n  - `timestamp:>=…Z timestamp:<…Z` and parenthesised `OR` groups.\n- **Aggregates:**\n  - `count()`.\n  - `p50()`, `p75()`, `p95()` and `p99()`, of `span.duration` (ms), `measurements.lcp`, `measurements.fcp`, `measurements.ttfb`, `measurements.cls`, `gen_ai.usage.input_tokens` or `tags[gen_ai.usage.input_tokens,number]`.\n  - `failure_rate()`: the share of spans whose `span.status` is not `ok`, `cancelled` or `unknown`. It reconciles exactly with `count()` and `!span.status:[ok,cancelled,unknown]`.\n- **Sample fields:** `span.duration`, `trace`, `id`, `timestamp`, `release`. Sorting works by `id`, `-id` and `-timestamp`.\n- **`span.status` values seen:** `ok`, `internal_error`, `resource_exhausted`, `cancelled`, `deadline_exceeded`, `invalid_argument`, `not_found`, `already_exists`.\n- **Rejected:** wildcards on `id` such as `id:0*`; span ids must be full 16-character hex.\n\n## Pitfalls\n\n- **Environment names collide.** Builder's dev deploy and every Vercel preview share `app:preview-behave:cloud`. Tell them apart with `release`.\n- **Proxy routes measure the backend.** `GET /api/proxy/[...path]` mostly measures the backend behind it, not the frontend release, and a few polling pages contribute most of its spans.\n- **Middleware durations are quantized** to about 1 ms. A one-step move in p95 reads as +100%; look at the absolute change.\n- **Long-lived transactions measure session length.** `/ws` and `/api/chat/sessions/{session_id}/stream` last minutes to an hour, not request latency. Compare per-turn or per-node transactions instead.\n- **Weekday and hour of day move latency.** Saturday-to-Sunday against Sunday-to-Monday is not a fair pair for a release comparison. Use full days, the same hours, and the A/A check.\n- **Do not rerun until it says faster.** Repeating runs, or trying many transactions and reporting the best, turns noise into wins. Decide the transaction, windows and settings first, then run once and replicate once.\n\n## Tests\n\n```bash\npython3 -m unittest discover -s .claude/skills/perf-compare/scripts\n```\n\nThe tests run offline in a few seconds, against the synthetic Sentry responses in `scripts/fixtures/`, served by a fake transport or by a local `http.server`. They cover:\n\n- query building, time parsing and option validation;\n- cursor pagination, retries, and real-socket errors;\n- the percentile, bootstrap and Fisher maths with fixed seeds;\n- seeded A/A calibration on independent and trace-clustered data;\n- the verdict rules, every exit code, and the missing-token error.\n\n<!-- File: .claude/skills/pr-address/SKILL.md (Tokens: ~7822 | Category: anthropic-skill) -->\n---\nname: pr-address\ndescription: Address PR review comments and loop until CI green and all comments resolved. TRIGGER when user asks to address comments, fix PR feedback, respond to reviewers, or babysit/monitor a PR.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Address\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nUnderstand the **Why / What / How** before addressing comments — you need context to make good fixes:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\n> If GraphQL is rate-limited, `gh pr view` fails. See [GitHub rate limits](#github-rate-limits) for REST fallbacks.\n\n## Optional: trigger the review bot\n\n**Only relevant if the PR has no review yet.** If the fetch steps below turn up no reviews and no inline threads, there is nothing to address *yet* — that is not the same as being done. You can summon the repo's review bot yourself:\n\n```bash\ngh pr comment {N} --body \"/review\"\n```\n\nThis is an **option, not a required step**. Skip it whenever the PR already has reviews (bot or human) — those are what you are here to address. See the `open-pr` skill's \"Review workflow\" step for the same trigger in the PR-creation flow.\n\n`autogpt-pr-reviewer[bot]` typically responds within ~30 minutes. **Poll for it rather than sleeping** — the loop in [Polling for CI + new comments](#polling-for-ci--new-comments) already re-checks every source every 30 seconds, so run that loop and react the moment the review lands. Note the `id` and `created_at` of the `/review` comment you posted; [Waiting on a requested review](#waiting-on-a-requested-review) needs them for the exit condition.\n\n## Fetch comments (all sources)\n\n### 1. Inline review threads — GraphQL (primary source of actionable items)\n\n> ⚠️ **WARNING — PAGINATE ALL PAGES BEFORE ADDRESSING ANYTHING**\n>\n> `reviewThreads(first: 100)` returns at most 100 threads per page AND returns threads **oldest-first**. On a PR with many review cycles (e.g. 373 threads), the oldest 100–200 threads are from past cycles and are **all already resolved**. Filtering client-side with `select(.isResolved == false)` on page 1 therefore yields **0 results** — even though pages 2–4 contain many unresolved threads from recent review cycles.\n>\n> **This is the most common failure mode:** agent fetches page 1, sees 0 unresolved after filtering, stops pagination, reports \"done\" — while hundreds of unresolved threads sit on later pages.\n>\n> One observed PR had 142 total threads: page 1 returned 0 unresolved (all old/resolved), while pages 2–3 had 111 unresolved. Another with 373 threads across 4 pages also had page 1 entirely resolved.\n>\n> **The rule: ALWAYS paginate to `hasNextPage == false` regardless of the per-page unresolved count. Never stop early because a page returns 0 unresolved.**\n\n**Step 1 — Fetch total count and sanity-check the newest threads:**\n\n```bash\n# Get total count and the newest 100 threads (last: 100 returns newest-first)\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n      newest: reviewThreads(last: 100) {\n        nodes { isResolved }\n      }\n    }\n  }\n}' | jq '{ total: .data.repository.pullRequest.reviewThreads.totalCount, newest_unresolved: [.data.repository.pullRequest.newest.nodes[] | select(.isResolved == false)] | length }'\n```\n\nIf `total > 100`, you have multiple pages — you **must** paginate all of them regardless of what `newest_unresolved` shows. The `last: 100` check is a sanity signal only; the full loop below is mandatory.\n\n**Step 2 — Collect all unresolved thread IDs across all pages:**\n\n```bash\n# Accumulate all unresolved threads — loop until hasNextPage == false\nCURSOR=\"\"\nALL_THREADS=\"[]\"\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes {\n            id\n            isResolved\n            path\n            line\n            comments(last: 1) {\n              nodes { databaseId body author { login } }\n            }\n          }\n        }\n      }\n    }\n  }\")\n  # Append unresolved nodes from this page\n  PAGE_THREADS=$(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)]')\n  ALL_THREADS=$(echo \"$ALL_THREADS $PAGE_THREADS\" | jq -s 'add')\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\n\n# Reverse so newest threads (last pages) are addressed first — GitHub returns oldest-first\n# and the most recent review cycle's comments are the ones blocking approval.\nALL_THREADS=$(echo \"$ALL_THREADS\" | jq 'reverse')\n\necho \"Total unresolved threads: $(echo \"$ALL_THREADS\" | jq 'length')\"\necho \"$ALL_THREADS\" | jq '[.[] | {id, path, line, body: .comments.nodes[0].body[:200]}]'\n```\n\n**Step 3 — Address every thread in `ALL_THREADS`, then resolve.**\n\nOnly after this loop completes (all pages fetched, count confirmed) should you begin making fixes.\n\n> **Why reverse?** GraphQL returns threads oldest-first and exposes no `orderBy` option. A PR with 373 threads has ~4 pages; threads from the latest review cycle land on the last pages. Processing in reverse ensures the newest, most blocking comments are addressed first — the earlier pages mostly contain outdated threads from prior cycles.\n\n**Filter to unresolved threads only** — skip any thread where `isResolved: true`. `comments(last: 1)` returns the most recent comment in the thread — act on that; it reflects the reviewer's final ask. Use the thread `id` (Relay global ID) to track threads across polls.\n\n> If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the REST fallback (flat comment list — no thread grouping or `isResolved`).\n\n### 2. Top-level reviews — REST (MUST paginate)\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits or outages. Continue polling reviews normally even when GraphQL is exhausted.**\n\n**CRITICAL — always `--paginate`.** Reviews default to 30 per page. PRs can have 80–170+ reviews (mostly empty resolution events). Without pagination you miss reviews past position 30 — including `autogpt-pr-reviewer[bot]`'s structured review which is typically posted after several CI runs and sits well beyond the first page.\n\nTwo things to extract:\n- **Overall state**: look for `CHANGES_REQUESTED` or `APPROVED` reviews.\n- **Actionable feedback**: non-empty bodies only. Empty-body reviews are thread-resolution events — they indicate progress but have no feedback to act on.\n\n**Where each reviewer posts:**\n- `autogpt-pr-reviewer[bot]` — posts detailed structured reviews (\"Blockers\", \"Should Fix\", \"Nice to Have\") as **top-level reviews**. Not present on every PR. Address ALL items.\n- `sentry[bot]` — posts bug predictions as **inline threads**. Fix real bugs, explain false positives.\n- `coderabbitai[bot]` — posts summaries as **top-level reviews** AND actionable items as **inline threads**. Address actionable items.\n- Human reviewers — can post in any source. Address ALL non-empty feedback.\n\n### 3. PR conversation comments — REST\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate\n```\n\n> **Already REST — unaffected by GraphQL rate limits.**\n\nMostly contains: bot summaries (`coderabbitai[bot]`), CI/conflict detection (`github-actions[bot]`), and author status updates. Scan for non-empty messages from non-bot human reviewers that aren't the PR author — those are the ones that need a response.\n\n**Slash-command trigger comments are never actionable items — skip them.** A comment whose entire body is a slash command (`/review` being the common one) exists to summon the review bot; it is not a request directed at you. Do not reply to it, do not treat it as feedback, and do not count it as a \"new comment\" that restarts the loop. The non-author filter above happens to hide the usual case where the PR author posted it, but that is incidental — a maintainer can post `/review` on someone else's PR, and it must still be skipped. The actionable content is the review it produces, which arrives as a top-level review and/or inline threads, not as a conversation comment.\n\n## For each unaddressed comment\n\n**CRITICAL: The only valid sequence is fix → commit → push → reply → resolve. Never resolve a thread without a real code commit.**\n\nResolving a thread via `resolveReviewThread` without an actual fix is the most common failure mode — it makes unresolved counts drop without any real change, producing a false \"done\" signal. If the issue was genuinely a false positive (no code change needed), reply explaining why and then resolve. Otherwise:\n\nAddress comments **one at a time**: fix → commit → push → inline reply → resolve.\n\n1. Read the referenced code, make the fix (or reply explaining why it's not needed)\n2. Commit and push the fix\n3. Reply **inline** (not as a new top-level comment) referencing the fixing commit — this is what resolves the conversation for bot reviewers (coderabbitai, sentry):\n\nUse a **markdown commit link** so GitHub renders it as a clickable reference. Always get the full SHA with `git rev-parse HEAD` **after** committing — never copy a SHA from a previous commit or hardcode one:\n\n```bash\nFULL_SHA=$(git rev-parse HEAD)\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies \\\n  -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): <description>\"\n```\n\n| Comment type | How to reply |\n|---|---|\n| Inline review (`pulls/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID}/replies -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n| Conversation (`issues/{N}/comments`) | `gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments -f body=\"🤖 Fixed in [abc1234](https://github.com/Significant-Gravitas/AutoGPT/commit/FULL_SHA): <description>\"` |\n\n### What counts as a valid resolution\n\nOnly two situations justify calling `resolveReviewThread`:\n\n1. **Real code fix**: you changed the code, committed + pushed, and replied with the SHA. The commit diff must actually address the concern — not just touch the same file.\n2. **Genuine false positive**: the reviewer's concern does not apply to this code, and you can give a specific technical reason (e.g. \"Not applicable — `sdk_cwd` is pre-validated by `_make_sdk_cwd()` which applies normpath + prefix assertion before reaching this point\").\n\n**Anti-patterns that look resolved but aren't — never do these:**\n- `\"Accepted, tracked as follow-up\"` — a deferral, not a fix. The concern is still open. Do not resolve.\n- `\"Acknowledged\"` or `\"Same as above\"` — these are acknowledgements, not fixes. Do not resolve.\n- `\"Fixed in abc1234\"` where `abc1234` is a commit that doesn't actually change the flagged line/logic — dishonest. Verify `git show abc1234 -- path/to/file` changes the right thing before posting.\n- Resolving without replying — the reviewer never sees what happened.\n\nWhen in doubt: if a code change is needed, make it. A deferred issue means the thread stays open until the follow-up PR is merged.\n\n## Codecov coverage\n\nCodecov patch target is **80%** on changed lines. Checks are **informational** (not blocking) but should be green.\n\n### Running coverage locally\n\n**Backend** (from `autogpt_platform/backend/`):\n```bash\npoetry run pytest -s -vv --cov=backend --cov-branch --cov-report term-missing\n```\n\n**Frontend** (from `autogpt_platform/frontend/`):\n```bash\npnpm vitest run --coverage\n```\n\n### When codecov/patch fails\n\n1. Find uncovered files: `git diff --name-only $(gh pr view --json baseRefName --jq '.baseRefName')...HEAD`\n2. For each uncovered file — extract inline logic to `helpers.ts`/`helpers.py` and test those (highest ROI). Colocate tests as `*_test.py` (backend) or `__tests__/*.test.ts` (frontend).\n3. Run coverage locally to verify, commit, push.\n\n## Format and commit\n\nAfter fixing, format the changed code:\n\n- **Backend** (from `autogpt_platform/backend/`): `poetry run format`\n- **Frontend** (from `autogpt_platform/frontend/`): `pnpm format && pnpm lint && pnpm types`\n\nIf API routes changed, regenerate the frontend client:\n```bash\ncd autogpt_platform/backend && poetry run rest &\nREST_PID=$!\ntrap \"kill $REST_PID 2>/dev/null\" EXIT\nWAIT=0; until curl -sf http://localhost:8006/health > /dev/null 2>&1; do sleep 1; WAIT=$((WAIT+1)); [ $WAIT -ge 60 ] && echo \"Timed out\" && exit 1; done\ncd ../frontend && pnpm generate:api:force\nkill $REST_PID 2>/dev/null; trap - EXIT\n```\nNever manually edit files in `src/app/api/__generated__/`.\n\nThen commit and **push immediately** — never batch commits without pushing. Each fix should be visible on GitHub right away so CI can start and reviewers can see progress.\n\n**Never push empty commits** (`git commit --allow-empty`) to re-trigger CI or bot checks. When a check fails, investigate the root cause (unchecked PR checklist, unaddressed review comments, code issues) and fix those directly. Empty commits add noise to git history.\n\nFor backend commits in worktrees: `poetry run git commit` (pre-commit hooks).\n\n## Coverage\n\nCodecov enforces patch coverage on new/changed lines — new code you write must be tested. Before pushing, verify you haven't left new lines uncovered:\n\n```bash\ncd autogpt_platform/backend\npoetry run pytest --cov=. --cov-report=term-missing {path/to/changed/module}\n```\n\nLook for lines marked `miss` — those are uncovered. Add tests for any new code you wrote as part of addressing comments.\n\n**Rules:**\n- New code you add should have tests\n- Don't remove existing tests when fixing comments\n- If a reviewer asks you to delete code, also delete its tests, but verify coverage hasn't dropped on remaining lines\n\n## The loop\n\n```text\naddress comments → format → commit → push\n→ wait for CI (while addressing new comments) → fix failures → push\n→ re-check comments after CI settles\n→ repeat until: all comments addressed AND CI green AND no new comments arriving\n```\n\n### Polling for CI + new comments\n\nAfter pushing, poll for **both** CI status and new comments in a single loop. Do not use `gh pr checks --watch` — it blocks the tool and prevents reacting to new comments while CI is running.\n\n> **Note:** `gh pr checks --watch --fail-fast` is tempting but it blocks the entire Bash tool call, meaning the agent cannot check for or address new comments until CI fully completes. Always poll manually instead.\n\n**Polling loop — repeat every 30 seconds:**\n\n1. Check CI status:\n```bash\ngh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,name,link\n```\n   Parse the results: if every check has `bucket` of `\"pass\"` or `\"skipping\"`, CI is green. If any has `\"fail\"`, CI has failed. Otherwise CI is still pending.\n\n2. Check for merge conflicts:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json mergeable --jq '.mergeable'\n```\n   If the result is `\"CONFLICTING\"`, the PR has a merge conflict — see \"Resolving merge conflicts\" below. If `\"UNKNOWN\"`, GitHub is still computing mergeability — wait and re-check next poll.\n\n3. Check for new/changed comments (all three sources):\n\n   **Inline threads** — re-run the GraphQL query from \"Fetch comments\". For each unresolved thread, record `{thread_id, last_comment_databaseId}` as your baseline. On each poll, action is needed if:\n   - A new thread `id` appears that wasn't in the baseline (new thread), OR\n   - An existing thread's `last_comment_databaseId` has changed (new reply on existing thread)\n\n   **Conversation comments:**\n   ```bash\n   # Exclude slash-command triggers here, not just when reading — otherwise a\n   # `/review` (yours or a maintainer's) counts as a \"new comment\" and forces\n   # an address round over a comment with nothing to address.\n   gh api repos/Significant-Gravitas/AutoGPT/issues/{N}/comments --paginate --slurp \\\n     | jq '[.[][] | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)]'\n   ```\n   Compare total count and newest `id` against baseline. Filter to non-empty, non-bot, non-author-update messages.\n\n   **Top-level reviews:**\n   ```bash\n   gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews --paginate\n   ```\n   Watch for new non-empty reviews (`CHANGES_REQUESTED` or `COMMENTED` with body). Compare total count and newest `id` against baseline.\n\n4. **React in this precedence order (first match wins):**\n\n| What happened | Action |\n|---|---|\n| Merge conflict detected | See \"Resolving merge conflicts\" below. |\n| Mergeability is `UNKNOWN` | GitHub is still computing mergeability. Sleep 30 seconds, then restart polling from the top. |\n| New comments detected | Address them (fix → commit → push → reply). After pushing, re-fetch all comments to update your baseline, then restart this polling loop from the top (new commits invalidate CI status). |\n| CI failed (bucket == \"fail\") | Get failed check links: `gh pr checks {N} --repo Significant-Gravitas/AutoGPT --json bucket,link --jq '.[] \\| select(.bucket == \"fail\") \\| .link'`. Extract run ID from link (format: `.../actions/runs/<run-id>/job/...`), read logs with `gh run view <run-id> --repo Significant-Gravitas/AutoGPT --log-failed`. Fix → commit → push → restart polling. |\n| CI green + no new comments, but a requested `/review` is unanswered | The bot is still working — quiet polls are exactly what that looks like. Keep polling; do **not** count these as the quiet polls below. See \"Waiting on a requested review\". |\n| CI green + no new comments | **Do not exit immediately.** Bots (coderabbitai, sentry) often post reviews shortly after CI settles. Continue polling for **2 more cycles (60s)** after CI goes green. Only exit after 2 consecutive green+quiet polls. |\n| CI pending + no new comments | Sleep 30 seconds, then poll again. |\n\n**The loop ends when:** CI fully green + all comments addressed + **2 consecutive polls with no new comments after CI settled** + no requested-but-unanswered `/review` (see below).\n\n### Waiting on a requested review\n\nIf a `/review` comment was posted on this PR and the bot has not answered it yet, the \"2 consecutive green+quiet polls\" exit is **premature** — a pending bot review looks identical to a quiet PR. Keep polling until one of these is true:\n\n- **The review arrived.** An answer is *either* a top-level review or an inline review comment from `autogpt-pr-reviewer[bot]`, whichever is later — an inline-only reply is still an answer, and counting only top-level reviews leaves the gate waiting after its threads have already been addressed. Once it lands, treat it as \"New comments detected\" and address it normally.\n- **The budget ran out.** Stop waiting **60 minutes** after the `/review` comment's `created_at`. Then exit per the normal rule and tell the user the requested review never arrived. Never hang indefinitely on a bot.\n\nRecompute both timestamps from the API on every poll rather than tracking\nelapsed time in a loop variable — a counter that some branch forgets to\nincrement is how this gate becomes an infinite wait.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\nA `/review` posted before this session still counts. Budget is **60 minutes**\nfrom `REQUESTED_AT`: an observed reply on this repo took 46 minutes, so 45\nwould have abandoned a review that was about to land. Enforce it in the same\npoll so the loop cannot wait forever:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\n**Do not post another `/review` while one is unanswered** — check this same\ncondition before triggering, or each round queues a duplicate request.\n\n### Resolving merge conflicts\n\n1. Identify the PR's target branch and remote:\n```bash\ngh pr view {N} --repo Significant-Gravitas/AutoGPT --json baseRefName --jq '.baseRefName'\ngit remote -v   # find the remote pointing to Significant-Gravitas/AutoGPT (typically 'upstream' in forks, 'origin' for direct contributors)\n```\n\n2. Pull the latest base branch with a 3-way merge:\n```bash\ngit pull {base-remote} {base-branch} --no-rebase\n```\n\n3. Resolve conflicting files, then verify no conflict markers remain:\n```bash\nif grep -R -n -E '^(<<<<<<<|=======|>>>>>>>)' <conflicted-files>; then\n  echo \"Unresolved conflict markers found — resolve before proceeding.\"\n  exit 1\nfi\n```\n\n4. Stage and push:\n```bash\ngit add <conflicted-files>\ngit commit -m \"Resolve merge conflicts with {base-branch}\"\ngit push\n```\n\n5. Restart the polling loop from the top — new commits reset CI status.\n\n## GitHub rate limits\n\nThree distinct rate limits exist — they have different causes, error shapes, and recovery times:\n\n| Error | HTTP code | Cause | Recovery |\n|---|---|---|---|\n| `{\"code\":\"abuse\"}` | 403 | Secondary rate limit — too many write operations (comments, mutations) in a short window | Wait **2–3 minutes**. 60s is often not enough. |\n| `{\"message\":\"API rate limit exceeded\"}` | 429 | Primary REST rate limit — 5000 calls/hr per user | Wait until `X-RateLimit-Reset` header timestamp |\n| `GraphQL: API rate limit already exceeded for user ID ...` | 403 on stderr, `gh` exits 1 | **GraphQL-specific** per-user limit — distinct from REST's 5000/hr and from the abuse secondary limit. Trips faster than REST because point costs per query. | Wait until the GraphQL window resets (typically ~1 hour from the first call in the window). REST still works — use fallbacks below. |\n\n**Prevention:** Add `sleep 3` between individual thread reply API calls. When posting >20 replies, increase to `sleep 5`.\n\n### Detection\n\nThe `gh` CLI surfaces the GraphQL limit on stderr with the exact string `GraphQL: API rate limit already exceeded for user ID <id>` and exits 1 — any `gh api graphql ...` **or** `gh pr view ...` call fails. Check current quota and reset time via the REST endpoint that reports GraphQL quota (this call is REST and still works whether GraphQL is rate-limited OR fully down):\n\n```bash\ngh api rate_limit --jq '.resources.graphql'   # { \"limit\": 5000, \"used\": 5000, \"remaining\": 0, \"reset\": 1729...}\n# Human-readable reset:\ngh api rate_limit --jq '.resources.graphql.reset' | xargs -I{} date -r {}\n```\n\nRetry when `remaining > 0`. If you need to proceed sooner, sleep 2–5 min and probe again — the limit is per user, not per machine, so other concurrent agents under the same token also consume it.\n\n### What keeps working\n\nWhen GraphQL is unavailable (rate-limited or outage):\n\n- **Keeps working (REST):** top-level reviews fetch, conversation comments fetch, all inline-comment replies, CI status (`gh pr checks`), and the `gh api rate_limit` probe.\n- **Degraded:** inline thread list — fall back to flat `/pulls/{N}/comments` REST, which drops thread grouping, `isResolved`, and Relay thread IDs. You still get comment bodies and the `databaseId` as `id`, enough to read and reply.\n- **Blocked:** `gh pr view`, the `resolveReviewThread` mutation, and any new `gh api graphql` queries — wait for the quota to reset.\n\n### Fall back to REST\n\n**PR metadata reads** — `gh pr view` uses GraphQL under the hood; use the REST pulls endpoint instead, which returns the full PR object:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.body'           # == --json body\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.base.ref'       # == --json baseRefName\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.mergeable'      # == --json mergeable\n```\n\nNote: REST `mergeable` returns `true|false|null`; GraphQL returns `MERGEABLE|CONFLICTING|UNKNOWN`. The `null` case maps to `UNKNOWN` — treat it the same (still computing; poll again).\n\n**Inline comments (flat list)** — no thread grouping or `isResolved`, but enough to read and reply:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate \\\n  | jq '[.[] | {id, path, line, user: .user.login, body: .body[:200], in_reply_to_id}]'\n```\n\nUse this degraded mode to make progress on the fix → reply loop, then return to GraphQL for `resolveReviewThread` once the rate limit resets.\n\n**Replies** — already REST-native (`/pulls/{N}/comments/{ID}/replies`); no change needed, use the same command as the main flow.\n\n**`resolveReviewThread`** — **no REST equivalent**; GitHub does not expose a REST endpoint for thread resolution. Queue the thread IDs needing resolution, wait for the GraphQL limit to reset, then run the resolve mutations in a batch (with `sleep 3` between calls, per the secondary-limit guidance).\n\n### Recovery from secondary rate limit (403 abuse)\n\n1. Stop all API writes immediately\n2. Wait **2 minutes minimum** (not 60s — secondary limits are stricter)\n3. Resume with `sleep 3` between each call\n4. If 403 persists after 2 min, wait another 2 min before retrying\n\nNever batch all replies in a tight loop — always space them out.\n\n## Parallel thread resolution\n\nWhen a PR has more than 10 unresolved threads, addressing one commit per thread is slow. Use this strategy instead:\n\n### Group by file, batch per commit\n\n1. Sort `ALL_THREADS` by `path` — threads in the same file can share a single commit.\n2. Fix all threads in one file → `git commit` → `git push` → reply to **all** those threads with the same SHA → resolve them all.\n3. Move to the next file group and repeat.\n\nThis reduces N commits to (number of files touched), which is usually 3–5 instead of 15–30.\n\n### Posting replies concurrently (for large batches)\n\nFor truly independent thread groups (different files, no shared logic), you can post replies in parallel using background subshells — but always space out API writes:\n\n```bash\n# Post replies to a batch of threads concurrently, 3s apart\n(\n  sleep 3\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID1}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\n(\n  sleep 6\n  gh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments/{ID2}/replies \\\n    -f body=\"🤖 Fixed in [${FULL_SHA:0:9}](https://github.com/Significant-Gravitas/AutoGPT/commit/${FULL_SHA}): ...\"\n) &\nwait  # wait for all background replies before resolving\n```\n\nThen resolve sequentially (GraphQL mutations):\n```bash\nfor THREAD_ID in \"$THREAD1\" \"$THREAD2\" \"$THREAD3\"; do\n  gh api graphql -f query=\"mutation { resolveReviewThread(input: {threadId: \\\"${THREAD_ID}\\\"}) { thread { isResolved } } }\"\n  sleep 3\ndone\n```\n\n**Always sleep 3s between individual API writes** — GitHub's secondary rate limit (403) triggers on bursts of >20 writes. Increase to `sleep 5` when posting more than 20 replies in a batch.\n\n## Resolving threads via GraphQL\n\nUse `resolveReviewThread` **only after** the commit is pushed and the reply is posted:\n\n```bash\ngh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"THREAD_ID\"}) { thread { isResolved } } }'\n```\n\n**Never call this mutation before committing the fix.** The orchestrator will verify actual unresolved counts via GraphQL after you output `ORCHESTRATOR:DONE` — false resolutions will be caught and you will be re-briefed.\n\n> `resolveReviewThread` is GraphQL-only — no REST equivalent. If GraphQL is rate-limited, see [GitHub rate limits](#github-rate-limits) for the queue-and-retry flow.\n\n### Verify actual count before outputting ORCHESTRATOR:DONE\n\nBefore claiming \"0 unresolved threads\", always query GitHub directly — don't rely on your own bookkeeping. Paginate all pages — a single `first: 100` query misses threads beyond page 1:\n\n```bash\n# Step 1: get total thread count\ngh api graphql -f query='\n{\n  repository(owner: \"Significant-Gravitas\", name: \"AutoGPT\") {\n    pullRequest(number: {N}) {\n      reviewThreads { totalCount }\n    }\n  }\n}' | jq '.data.repository.pullRequest.reviewThreads.totalCount'\n\n# Step 2: paginate all pages, count truly unresolved\nCURSOR=\"\"; UNRESOLVED=0\nwhile true; do\n  AFTER=${CURSOR:+\", after: \\\"$CURSOR\\\"\"}\n  PAGE=$(gh api graphql -f query=\"\n  {\n    repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n      pullRequest(number: {N}) {\n        reviewThreads(first: 100${AFTER}) {\n          pageInfo { hasNextPage endCursor }\n          nodes { isResolved }\n        }\n      }\n    }\n  }\")\n  UNRESOLVED=$(( UNRESOLVED + $(echo \"$PAGE\" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))\n  HAS_NEXT=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')\n  CURSOR=$(echo \"$PAGE\" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')\n  [ \"$HAS_NEXT\" = \"false\" ] && break\ndone\necho \"Unresolved threads: $UNRESOLVED\"\n```\n\nOnly output `ORCHESTRATOR:DONE` after this loop reports 0.\n\n<!-- File: .claude/skills/pr-polish/SKILL.md (Tokens: ~5018 | Category: anthropic-skill) -->\n---\nname: pr-polish\ndescription: Alternate /pr-review and /pr-address on a PR until the PR is truly mergeable — no new review findings, zero unresolved inline threads, zero unaddressed top-level reviews or issue comments, all CI checks green, and two consecutive quiet polls after CI settles. Use when the user wants a PR polished to merge-ready without setting a fixed number of rounds.\nuser-invocable: true\nargument-hint: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Polish\n\n**Goal.** Drive a PR to merge-ready by alternating `/pr-review` and `/pr-address` until **all** of the following hold:\n\n1. The most recent `/pr-review` produces **zero new findings** (no new inline comments, no new top-level reviews with a non-empty body).\n2. Every inline review thread reachable via GraphQL reports `isResolved: true`.\n3. Every non-bot, non-author top-level review has been acknowledged (replied-to) OR resolved via a thread it spawned.\n4. Every non-bot, non-author issue comment has been acknowledged (replied-to).\n5. Every CI check is `conclusion: \"success\"` or `\"skipped\"` / `\"neutral\"` — none `\"failure\"` or still pending.\n6. **Two consecutive post-CI polls** (≥60s apart) stay clean — no new threads, no new non-empty reviews, no new issue comments. Bots (coderabbitai, sentry, autogpt-pr-reviewer) frequently post late after CI settles; a single green snapshot is not sufficient.\n7. **No requested-but-unanswered review.** If a `/review` comment was posted on the PR and the review bot has not replied yet, quiet polls do **not** count toward condition 6 — keep polling until the review lands or the wait budget expires. See [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n**Do not stop at a fixed number of rounds.** If round N introduces new comments, round N+1 is required. Cap at `_MAX_ROUNDS = 10` as a safety valve, but expect 2–5 in practice.\n\n## TodoWrite\n\nBefore starting, write two todos so the user can see the loop progression:\n\n- `Round {current}: /pr-review + /pr-address on PR #{N}` — current iteration.\n- `Final polish polling: 2 consecutive clean polls, CI green, 0 unresolved` — runs after the last non-empty review round.\n\nUpdate the `current` round counter at the start of each iteration; mark `completed` only when the round's address step finishes (all new threads addressed + resolved).\n\n## Find the PR\n\n```bash\nARG_PR=\"${ARG:-}\"\n# Normalize URL → numeric ID if the skill arg is a pull-request URL.\nif [[ \"$ARG_PR\" =~ ^https?://github\\.com/[^/]+/[^/]+/pull/([0-9]+) ]]; then\n  ARG_PR=\"${BASH_REMATCH[1]}\"\nfi\nPR=\"${ARG_PR:-$(gh pr list --head \"$(git branch --show-current)\" --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')}\"\nif [ -z \"$PR\" ] || [ \"$PR\" = \"null\" ]; then\n  echo \"No PR found for current branch. Provide a PR number or URL as the skill arg.\"\n  exit 1\nfi\necho \"Polishing PR #$PR\"\n```\n\n## The outer loop\n\n```text\nround = 0\nwhile round < _MAX_ROUNDS:\n    round += 1\n    baseline = snapshot_state(PR)   # see \"Snapshotting state\" below\n    invoke_skill(\"pr-review\", PR)   # posts findings as inline comments / top-level review\n    findings = diff_state(PR, baseline)\n    if findings.total == 0:\n        break  # no new findings → go to polish polling\n    invoke_skill(\"pr-address\", PR)  # resolves every unresolved thread + CI failure\n# Post-loop: polish polling (see below).\npolish_polling(PR)\n```\n\n### Optional: the review bot\n\n`invoke_skill(\"pr-review\", PR)` is the agent's **own** review and always runs every round. In addition to it, the repo's review bot can be summoned on demand by commenting `/review` on the PR (see the `open-pr` skill):\n\n```bash\ngh pr comment \"${PR}\" --body \"/review\"\n```\n\nThis is an **option, not a required step**, and it is **not** a substitute for the `/pr-review` round — use it when you want a second opinion the outer loop cannot produce itself, or when the PR carries no bot findings at all yet. Bot findings land as inline threads and top-level reviews, exactly like the agent's own findings, so both feed the same `invoke_skill(\"pr-address\", PR)` step — no separate handling is needed. If you do request one, the exit conditions must wait for it; see [Waiting on a requested bot review](#waiting-on-a-requested-bot-review).\n\n### Snapshotting state\n\nBefore each `/pr-review`, capture a baseline so the diff after the review reflects **only** what the review just added (not pre-existing threads):\n\n```bash\n# Inline threads — total count + latest databaseId per thread\ngh api graphql -f query=\"\n{\n  repository(owner: \\\"Significant-Gravitas\\\", name: \\\"AutoGPT\\\") {\n    pullRequest(number: ${PR}) {\n      reviewThreads(first: 100) {\n        totalCount\n        nodes {\n          id\n          isResolved\n          comments(last: 1) { nodes { databaseId } }\n        }\n      }\n    }\n  }\n}\" > /tmp/baseline_threads.json\n\n# Top-level reviews — count + latest id per non-empty review\ngh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate \\\n  --jq '[.[] | select((.body // \"\") != \"\") | {id, user: .user.login, state, submitted_at}]' \\\n  > /tmp/baseline_reviews.json\n\n# Issue comments — count + latest id per non-bot, non-author comment.\n# Bots are filtered by User.type == \"Bot\" (GitHub sets this for app/bot\n# accounts like coderabbitai, github-actions, sentry-io). The author is\n# filtered by comparing login to the PR author — export it so jq can see it.\nAUTHOR=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}\" --jq '.user.login')\n# Slash-command triggers are excluded here too: a `/review` posted by a\n# maintainer is non-bot and non-author, so without this it enters the baseline\n# and later registers as a \"new finding\", forcing an address round over a\n# comment with nothing to address.\ngh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq --arg author \"$AUTHOR\" \\\n      '[.[][] | select(.user.type != \"Bot\" and .user.login != $author)\n             | select((.body // \"\") | test(\"^\\\\s*/[a-z-]+\\\\s*$\") | not)\n             | {id, user: .user.login, created_at}]' \\\n  > /tmp/baseline_issue_comments.json\n```\n\n### Diffing after a review\n\nAfter `/pr-review` runs, any of these counting as \"new findings\" means another address round is needed:\n\n- New inline thread `id` not in the baseline.\n- An existing thread whose latest comment `databaseId` is higher than the baseline's (new reply on an old thread).\n- A new top-level review `id` with a non-empty body.\n- A new issue comment `id` from a non-bot, non-author user.\n\nIf any of the four buckets is non-empty → not done; invoke `/pr-address` and loop.\n\n## Polish polling\n\nOnce `/pr-review` produces zero new findings, do **not** exit yet. Bots (coderabbitai, sentry, autogpt-pr-reviewer) commonly post late reviews after CI settles — 30–90 seconds after the final push. Poll at 60-second intervals:\n\n```text\nNON_SUCCESS_TERMINAL = {\"failure\", \"cancelled\", \"timed_out\", \"action_required\", \"startup_failure\"}\nclean_polls = 0\nrequired_clean = 2\nwhile clean_polls < required_clean:\n    # 1. CI gate — any terminal non-success conclusion (not just \"failure\")\n    # must trigger /pr-address. \"success\", \"skipped\", \"neutral\" are clean;\n    # anything else (including cancelled, timed_out, action_required) is a\n    # blocker that won't self-resolve.\n    ci = fetch_check_runs(PR)\n    if any ci.conclusion in NON_SUCCESS_TERMINAL:\n        invoke_skill(\"pr-address\", PR)  # address failures + any new comments\n        baseline = snapshot_state(PR)   # reset — push during address invalidates old baseline\n        clean_polls = 0\n        continue\n    if any ci.conclusion is None (still in_progress):\n        sleep 60; continue  # wait without counting this as clean\n\n    # 2. Comment / thread gate\n    threads = fetch_unresolved_threads(PR)\n    new_issue_comments = diff_against_baseline(issue_comments)\n    new_reviews = diff_against_baseline(reviews)\n    if threads or new_issue_comments or new_reviews:\n        invoke_skill(\"pr-address\", PR)\n        baseline = snapshot_state(PR)   # reset — the address loop just dealt with these,\n                                        # otherwise they stay \"new\" relative to the old baseline forever\n        clean_polls = 0\n        continue\n\n    # 3. Mergeability gate\n    mergeable = gh api repos/.../pulls/${PR} --jq '.mergeable'\n    if mergeable == false (CONFLICTING):\n        resolve_conflicts(PR)  # see pr-address skill\n        clean_polls = 0\n        continue\n    if mergeable is null (UNKNOWN):\n        sleep 60; continue\n\n    # 4. Pending-review gate — a /review with no bot answer yet.\n    # Quiet is exactly what a bot still thinking looks like, so don't bank\n    # a clean poll on it. review_requested_but_unanswered() already returns\n    # false once REVIEW_WAIT_BUDGET is spent, so the loop cannot hang here.\n    if review_requested_but_unanswered(PR):\n        sleep 60; continue\n\n    clean_polls += 1\n    sleep 60\n```\n\nOnly after `clean_polls == 2` do you report `ORCHESTRATOR:DONE`.\n\n### Concrete CI fetch (don't parse `gh pr checks` text columns)\n\nThe `fetch_check_runs(PR)` step above must use `--json`, not the default text output. Job names can contain spaces and parentheses (e.g. `test (3.11)`, `Analyze (python)`), so `gh pr checks $PR | awk '{print $2}'` extracts `(3.11)` instead of the status — leading to a clean-poll firing while jobs are still pending.\n\n```bash\n# Reliable: use --json so columns are unambiguous.\nci_json=$(gh pr checks $PR --repo Significant-Gravitas/AutoGPT --json name,state,bucket)\npending=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"pending\")] | length')\nfailed=$(echo \"$ci_json\" | jq '[.[] | select(.bucket == \"fail\" or .bucket == \"cancel\")] | length')\n\n# Buckets are: pass | fail | pending | cancel | skipping\n# (NOTE: gh pr checks does NOT expose `conclusion` as a JSON field —\n# only `bucket`. Don't confuse with the GitHub REST API's check_runs\n# endpoint, which DOES use conclusion.)\n```\n\nMap back to the pseudocode above: `bucket == \"pending\"` is `ci.conclusion is None (still in_progress)`; `bucket in {\"fail\", \"cancel\"}` is `ci.conclusion in NON_SUCCESS_TERMINAL`; `bucket in {\"pass\", \"skipping\"}` is clean.\n\n### Waiting on a requested bot review\n\n`autogpt-pr-reviewer[bot]` typically takes ~30 minutes to answer a `/review` — far longer than the 60-second poll interval, so without this gate two clean polls would report `ORCHESTRATOR:DONE` while the requested review is still in flight.\n\n`review_requested_but_unanswered(PR)` is:\n\nRecompute from the API on **every** poll — never track elapsed time in a loop\nvariable, which is how this gate becomes an infinite wait. `--paginate`\nconcatenates one array per page, so `last` picks the last item of the final\npage rather than the newest overall; use `--slurp` and `max_by`. An answer is\n*either* a top-level review or an inline review comment, whichever is later —\nan inline-only reply is still an answer, and counting only top-level reviews\nleft the gate waiting after its threads had already been addressed.\n\n```bash\n# `--slurp` cannot be combined with `--jq`; pipe to standalone jq instead.\nREQUESTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/issues/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select((.body // \"\") | test(\"^\\\\s*/review\\\\s*$\"))] | max_by(.created_at) | .created_at // empty')\nREVIEWED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/reviews\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.submitted_at) | .submitted_at // empty')\nCOMMENTED_AT=$(gh api \"repos/Significant-Gravitas/AutoGPT/pulls/${PR}/comments\" --paginate --slurp \\\n  | jq -r '[.[][] | select(.user.login == \"autogpt-pr-reviewer[bot]\")] | max_by(.created_at) | .created_at // empty')\nANSWERED_AT=$(printf '%s\\n%s\\n' \"$REVIEWED_AT\" \"$COMMENTED_AT\" | grep -v '^$' | sort | tail -1)\n\n# Pending iff a request exists and no answer is strictly newer than it.\n# Start every poll from `false`: a stale `true` from the previous poll would\n# otherwise keep the gate waiting after the answer has already landed.\n# `[ a \\< b ]` is not portable (fails under zsh), so compare via sort.\npending=false\nNEWEST=$(printf '%s\\n%s\\n' \"$REQUESTED_AT\" \"$ANSWERED_AT\" | grep -v '^$' | sort | tail -1)\nif [ -n \"$REQUESTED_AT\" ] && { [ -z \"$ANSWERED_AT\" ] || [ \"$NEWEST\" = \"$REQUESTED_AT\" ]; }; then\n  pending=true\nfi\n```\n\n`REVIEW_WAIT_BUDGET = 60 minutes`, measured as `now - REQUESTED_AT`:\n\n```bash\n# Only meaningful when a request exists: GNU date reads an empty string as\n# \"now\", BSD date errors, and either way there is nothing to time out.\nif [ -n \"$REQUESTED_AT\" ]; then\n  WAITED=$(( $(date +%s) - $(date -u -j -f \"%Y-%m-%dT%H:%M:%SZ\" \"$REQUESTED_AT\" +%s 2>/dev/null || date -u -d \"$REQUESTED_AT\" +%s) ))\n  [ \"$WAITED\" -ge 3600 ] && pending=false   # budget spent — stop waiting\nfi\n```\n\nAn observed reply on this repo took **46 minutes**, so a 45-minute budget\nwould have abandoned a review that was about to arrive. 60 gives headroom.\n\nPast the budget, stop waiting, resume counting clean polls normally, and note\nin the final report that the requested bot review never arrived. The loop must\nnever hang on a bot.\n\n**Never post a second `/review` while one is unanswered.** Check\n`review_requested_but_unanswered(PR)` before triggering, not just before\nexiting — otherwise each round requests another review and the bot answers a\nqueue of duplicates.\n\n### Why 2 clean polls, not 1\n\nA single green snapshot can be misleading — the final CI check often completes ~30s before a bot posts its delayed review. One quiet cycle does not prove the PR is stable; two consecutive cycles with no new threads, reviews, or issue comments arriving gives high confidence nothing else is incoming.\n\n### Why checking every source each poll\n\n`/pr-address` polling inside a single round already re-checks its own comments, but `/pr-polish` sits a level above and must also catch:\n\n- New top-level reviews (autogpt-pr-reviewer sometimes posts structured feedback only after several CI green cycles).\n- Issue comments from human reviewers (not caught by inline thread polling).\n- Sentry bug predictions that land on new line numbers post-push.\n- Merge conflicts introduced by a race between your push and a merge to `dev`.\n\n## Invocation pattern\n\nDelegate to existing skills with the `Skill` tool; do not re-implement the review or address logic inline. This keeps the polish loop focused on orchestration and lets the child skills evolve independently.\n\n```python\nSkill(skill=\"pr-review\",  args=pr_url)\nSkill(skill=\"pr-address\", args=pr_url)\n```\n\nAfter each child invocation, re-query GitHub state directly — never trust a summary for the stop condition. The orchestrator's `ORCHESTRATOR:DONE` is verified against actual GraphQL / REST responses per the rules in `pr-address`'s \"Verify actual count before outputting ORCHESTRATOR:DONE\" section.\n\n### **Auto-continue: do NOT end your response between child skills**\n\n`/pr-polish` is a single orchestration task — one invocation drives the PR all the way to merge-ready. When a child `Skill()` call returns control to you:\n\n- Do NOT summarize and stop.\n- Do NOT wait for user confirmation to continue.\n- Immediately, in the same response, perform the next loop step: state diff → decide next action → next `Skill()` call or polling sleep.\n\nThe child skill returning is a **loop iteration boundary**, not a conversation turn boundary. You are expected to keep going until one of the exit conditions in the opening section is met (2 consecutive clean polls, `_MAX_ROUNDS` hit, or an unrecoverable error).\n\nIf the user needs to approve a risky action mid-loop (e.g., a force-push or a destructive git operation), pause there — but not at the routine \"round N finished, round N+1 needed\" boundary. Those are silent transitions.\n\n### **Run /pr-polish in the foreground — never in a background agent**\n\nSpawning `/pr-polish` inside an `Agent(subagent_type=\"general-purpose\")` background task **does not work**. Background agents don't inherit the parent's slash-command registry, so `Skill(skill=\"pr-review\")` and `Skill(skill=\"pr-address\")` calls aren't available — the agent has to manually replicate the child skills' logic, which is fragile and tends to stall on the first network or rate-limit hiccup. Symptom: the background task reports `stalled: no progress for 600s` mid-review.\n\nRun `/pr-polish` inline in the foreground conversation. If the user asks for \"/pr-polish + /pr-test in parallel\", split them: foreground `/pr-polish`, and ONLY then can the test step go to a background agent (because `/pr-test` doesn't itself need to invoke skills).\n\n### **You MUST invoke `Skill(pr-review)` every round — even when bot reviews already exist**\n\nA common failure mode: CodeRabbit / autogpt-pr-reviewer / Sentry have already posted findings on the PR, and the orchestrator skips the `Skill(pr-review)` step on the assumption that \"review has been done.\" That's wrong — the outer loop's purpose is to layer **the agent's own review** on top of the bot reviews, catching issues the bots miss (architecture, naming, cross-file invariants, hidden coupling). If the orchestrator only addresses bot findings without ever running its own review, the loop converges to \"bot-clean\" but not \"agent-reviewed-clean,\" and the user reasonably asks \"did /pr-polish even read the diff?\"\n\n**Self-check before reporting `ORCHESTRATOR:DONE`:** confirm at least one `Skill(skill=\"pr-review\")` call appears in the current orchestration. If none, the loop is incomplete — go back and run one round.\n\n## GitHub rate limits\n\nThis skill issues many GraphQL calls (one review-thread query per outer iteration plus per-poll queries inside polish polling). Expect the GraphQL budget to be tight on large PRs. When `gh api rate_limit --jq .resources.graphql.remaining` drops below ~200, back off:\n\n- Fall back to REST for reads (flat `/pulls/{N}/comments`, `/pulls/{N}/reviews`, `/issues/{N}/comments`) per the `pr-address` skill's GraphQL-fallback section.\n- Queue thread resolutions (GraphQL-only) until the budget resets; keep making progress on fixes + REST replies meanwhile.\n- `sleep 5` between any batch of ≥20 writes to avoid secondary rate limits.\n\n## Safety valves\n\n- `_MAX_ROUNDS = 10` — if review+address rounds exceed this, stop and escalate to the user with a summary of what's still unresolved. A PR that cannot converge in 10 rounds has systemic issues that need human judgment.\n- After each commit, run `poetry run format` / `pnpm format && pnpm lint && pnpm types` per the target codebase's conventions. A failing format check is CI `failure` that will never self-resolve.\n- Every `/pr-review` round checks for **duplicate** concerns first (via `pr-review`'s own \"Fetch existing review comments\" step) so the loop does not re-post the same finding that a prior round already resolved.\n\n## Reporting\n\nWhen the skill finishes (either via two clean polls or hitting `_MAX_ROUNDS`), produce a compact summary:\n\n```\nPR #{N} polish complete ({rounds_completed} rounds):\n- {X} inline threads opened and resolved\n- {Y} CI failures fixed\n- {Z} new commits pushed\nFinal state: CI green, {total} threads all resolved, mergeable.\n```\n\nIf exiting via `_MAX_ROUNDS`, flag explicitly:\n\n```\nPR #{N} polish stopped at {_MAX_ROUNDS} rounds — NOT merge-ready:\n- {N} threads still unresolved: {titles}\n- CI status: {summary}\nNeeds human review.\n```\n\n## When to use this skill\n\nUse when the user says any of:\n- \"polish this PR\"\n- \"keep reviewing and addressing until it's mergeable\"\n- \"loop /pr-review + /pr-address until done\"\n- \"make sure the PR is actually merge-ready\"\n\nDo **not** use when:\n- User wants just one review pass (→ `/pr-review`).\n- User wants to address already-posted comments without further self-review (→ `/pr-address`).\n- A fixed round count is explicitly requested (e.g., \"do 3 rounds\") — honour the count instead of converging.\n\n<!-- File: .claude/skills/pr-review/SKILL.md (Tokens: ~836 | Category: anthropic-skill) -->\n---\nname: pr-review\ndescription: Review a PR for correctness, security, code quality, and testing issues. TRIGGER when user asks to review a PR, check PR quality, or give feedback on a PR.\nuser-invocable: true\nargs: \"[PR number or URL] — if omitted, finds PR for current branch.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# PR Review\n\n## Find the PR\n\n```bash\ngh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT\ngh pr view {N}\n```\n\n## Read the PR description\n\nBefore reading code, understand the **why**, **what**, and **how** from the PR description:\n\n```bash\ngh pr view {N} --json body --jq '.body'\n```\n\nEvery PR should have a Why / What / How structure. If any of these are missing, note it as feedback.\n\n## Read the diff\n\n```bash\ngh pr diff {N}\n```\n\n## Fetch existing review comments\n\nBefore posting anything, fetch existing inline comments to avoid duplicates:\n\n```bash\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments --paginate\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/reviews\n```\n\n## What to check\n\n**Description quality:** Does the PR description cover Why (motivation/problem), What (summary of changes), and How (approach/implementation details)? If any are missing, request them — you can't judge the approach without understanding the problem and intent.\n\n**Correctness:** logic errors, off-by-one, missing edge cases, race conditions (TOCTOU in file access, credit charging), error handling gaps, async correctness (missing `await`, unclosed resources).\n\n**Security:** input validation at boundaries, no injection (command, XSS, SQL), secrets not logged, file paths sanitized (`os.path.basename()` in error messages).\n\n**Code quality:** apply rules from backend/frontend CLAUDE.md files and root `AGENTS.md`, including its \"User-facing copy\" section (text a person reads says workflow, never agent or graph).\n\n**Architecture:** DRY, single responsibility, modular functions. `Security()` vs `Depends()` for FastAPI auth. `data:` for SSE events, `: comment` for heartbeats. `transaction=True` for Redis pipelines.\n\n**Testing:** edge cases covered, colocated `*_test.py` (backend) / `__tests__/` (frontend), mocks target where symbol is **used** not defined, `AsyncMock` for async.\n\n## Output format\n\nEvery comment **must** be prefixed with `🤖` and a criticality badge:\n\n| Tier | Badge | Meaning |\n|---|---|---|\n| Blocker | `🔴 **Blocker**` | Must fix before merge |\n| Should Fix | `🟠 **Should Fix**` | Important improvement |\n| Nice to Have | `🟡 **Nice to Have**` | Minor suggestion |\n| Nit | `🔵 **Nit**` | Style / wording |\n\nExample: `🤖 🔴 **Blocker**: Missing error handling for X — suggest wrapping in try/except.`\n\n## Post inline comments\n\nFor each finding, post an inline comment on the PR (do not just write a local report):\n\n```bash\n# Get the latest commit SHA for the PR\nCOMMIT_SHA=$(gh api repos/Significant-Gravitas/AutoGPT/pulls/{N} --jq '.head.sha')\n\n# Post an inline comment on a specific file/line\ngh api repos/Significant-Gravitas/AutoGPT/pulls/{N}/comments \\\n  -f body=\"🤖 🔴 **Blocker**: <description>\" \\\n  -f commit_id=\"$COMMIT_SHA\" \\\n  -f path=\"<file path>\" \\\n  -F line=<line number>\n```\n\n<!-- File: .claude/skills/pr-test/SKILL.md (Tokens: ~13302 | Category: anthropic-skill) -->\n---\nname: pr-test\ndescription: \"E2E manual testing of PRs/branches using docker compose, agent-browser, and API calls. TRIGGER when user asks to manually test a PR, test a feature end-to-end, or run integration tests against a running system.\"\nuser-invocable: true\nargument-hint: \"[worktree path or PR number] — tests the PR in the given worktree. Optional flags: --fix (auto-fix issues found)\"\nmetadata:\n  author: autogpt-team\n  version: \"2.3.0\"\n---\n\n# Manual E2E Test\n\nTest a PR/branch end-to-end by building the full platform, interacting via browser and API, capturing screenshots, and reporting results.\n\n**Changelog 2.2.0** — auth flow updated for Better Auth (Supabase signup is\ngone), env-setup gaps closed, a proven Playwright fallback for agent-browser,\na billing-test trap that produces false passes, safer process cleanup, and a\nmock-provider pattern for deterministic $0 testing. Learned on\n[#14206](https://github.com/Significant-Gravitas/AutoGPT/pull/14206) — see the\n[evidence comment](https://github.com/Significant-Gravitas/AutoGPT/pull/14206#issuecomment-5511429524).\n**2.2.1** — corrects two claims from 2.2.0 that didn't survive live-stack\nverification (JWKS does **not** rotate on a frontend restart; the local\nPostgres port is `5432`, not `54322`) and hardens the auth setup (explicit\npassword-length/allowlist/rate-limit failure modes, fail-fast on an empty\ntoken, password kept out of process args).\n**2.3.0** — screenshots are posted as GitHub comment attachments\n(`gh pr comment --attach`, needs gh >= 2.99.0) instead of being pushed to a\n`test-screenshots/*` branch, which accumulated one branch per tested PR and\nmade every old report's images depend on that branch surviving.\n\n## Critical Requirements\n\nThese are NON-NEGOTIABLE. Every test run MUST satisfy ALL the following:\n\n### 1. Screenshots at Every Step\n- Take a screenshot at EVERY significant test step — not just at the end\n- Every test scenario MUST have at least one BEFORE and one AFTER screenshot\n- Name screenshots sequentially: `{NN}-{action}-{state}.png` (e.g., `01-credits-before.png`, `02-credits-after.png`)\n- If a screenshot is missing for a scenario, the test is INCOMPLETE — go back and take it\n\n### 2. Screenshots MUST Be Posted to PR\n- Attach ALL screenshots to the PR comment with `gh pr comment --attach` (see Step 7). **Never push image files to a repo branch** — attachments live in GitHub's own asset store and leave nothing behind in the repo\n- Post a PR comment with ALL screenshots embedded inline, each with its explanation\n- This is NOT optional — every test run MUST end with a PR comment containing screenshots\n- If the upload fails, retry. If it still fails, list failed files and require manual drag-and-drop/paste attachment in the PR comment, and mark the run INCOMPLETE\n\n### 3. State Verification with Before/After Evidence\n- For EVERY state-changing operation (API call, user action), capture the state BEFORE and AFTER\n- Log the actual API response values (e.g., `credits_before=100, credits_after=95`)\n- Screenshot MUST show the relevant UI state change\n- Compare expected vs actual values explicitly — do not just eyeball it\n\n### 4. Negative Test Cases Are Mandatory\n- Test at least ONE negative case per feature (e.g., insufficient credits, invalid input, unauthorized access)\n- Verify error messages are user-friendly and accurate\n- Verify the system state did NOT change after a rejected operation\n\n### 5. Test Report Must Include Full Evidence\nEach test scenario in the report MUST have:\n- **Steps**: What was done (exact commands or UI actions)\n- **Expected**: What should happen\n- **Actual**: What actually happened\n- **API Evidence**: Before/after API response values for state-changing operations\n- **Screenshot Evidence**: Before/after screenshots with explanations\n\n## State Manipulation for Realistic Testing\n\n**Billing-test trap — this one produces a false pass, not a visible failure.**\nLLM block cost filters key on the *platform-owned* credential id. A run made\nwith the test user's own API key bills nothing by design, so a credits\nbefore/after assertion silently passes on zero deltas either way. Any test\nthat verifies credit reconciliation MUST use the system credential, not a\nuser-supplied key. Related: Ollama block entries are configured with an\nexplicit `$0` run-based cost (`BlockCostType.RUN, cost_amount=0` in\n`block_cost_config.py`), not a token-metered one — so pre/post-flight cost\ndeltas are always 0 for them regardless of credential. Never use an Ollama\nmodel to test credit reconciliation; pick any hosted model billed through the\nsystem credential instead.\n\nWhen testing features that depend on specific states (rate limits, credits, quotas):\n\n1. **Use Redis CLI to set counters directly:**\n   ```bash\n   # Find the Redis container\n   REDIS_CONTAINER=$(docker ps --format '{{.Names}}' | grep redis | head -1)\n   # Set a key with expiry\n   docker exec $REDIS_CONTAINER redis-cli SET key value EX ttl\n   # Example: Set rate limit counter to near-limit\n   docker exec $REDIS_CONTAINER redis-cli SET \"rate_limit:user:$PR_TEST_USER_EMAIL\" 99 EX 3600\n   # Example: Check current value\n   docker exec $REDIS_CONTAINER redis-cli GET \"rate_limit:user:$PR_TEST_USER_EMAIL\"\n   ```\n\n2. **Use API calls to check before/after state:**\n   ```bash\n   # BEFORE: Record current state\n   BEFORE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits BEFORE: $BEFORE\"\n\n   # Perform the action...\n\n   # AFTER: Record new state and compare\n   AFTER=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   echo \"Credits AFTER: $AFTER\"\n   echo \"Delta: $(( BEFORE - AFTER ))\"\n   ```\n\n3. **Take screenshots BEFORE and AFTER state changes** — the UI must reflect the backend state change\n\n4. **Never rely on mocked/injected browser state** — always use real backend state. Do NOT use `agent-browser eval` to fake UI state. The backend must be the source of truth.\n\n5. **Use direct DB queries when needed:**\n   ```bash\n   # Query via Supabase's PostgREST or docker exec into the DB\n   docker exec supabase-db psql -U supabase_admin -d postgres -c \"SELECT credits FROM user_credits WHERE user_id = '...';\"\n   ```\n\n6. **After every API test, verify the state change actually persisted:**\n   ```bash\n   # Example: After a credits purchase, verify DB matches API\n   API_CREDITS=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/credits | jq '.credits')\n   DB_CREDITS=$(docker exec supabase-db psql -U supabase_admin -d postgres -t -c \"SELECT credits FROM user_credits WHERE user_id = '...';\" | tr -d ' ')\n   [ \"$API_CREDITS\" = \"$DB_CREDITS\" ] && echo \"CONSISTENT\" || echo \"MISMATCH: API=$API_CREDITS DB=$DB_CREDITS\"\n   ```\n\n## Arguments\n\n- `$ARGUMENTS` — worktree path (e.g. `$REPO_ROOT`) or PR number\n- If `--fix` flag is present, auto-fix bugs found and push fixes (like pr-address loop)\n\n## Step 0: Resolve the target\n\n```bash\n# If argument is a PR number, find its worktree\ngh pr view {N} --json headRefName --jq '.headRefName'\n# If argument is a path, use it directly\n```\n\nDetermine:\n- `REPO_ROOT` — the root repo directory: `git -C \"$WORKTREE_PATH\" worktree list | head -1 | awk '{print $1}'` (or `git rev-parse --show-toplevel` if not a worktree)\n- `WORKTREE_PATH` — the worktree directory\n- `PLATFORM_DIR` — `$WORKTREE_PATH/autogpt_platform`\n- `BACKEND_DIR` — `$PLATFORM_DIR/backend`\n- `FRONTEND_DIR` — `$PLATFORM_DIR/frontend`\n- `PR_NUMBER` — the PR number (from `gh pr list --head $(git branch --show-current)`)\n- `PR_TITLE` — the PR title, slugified (e.g. \"Add copilot permissions\" → \"add-copilot-permissions\")\n- `RESULTS_DIR` — `$REPO_ROOT/test-results/PR-{PR_NUMBER}-{slugified-title}`\n\nCreate the results directory:\n```bash\nPR_NUMBER=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json number --jq '.[0].number')\nPR_TITLE=$(cd $WORKTREE_PATH && gh pr list --head $(git branch --show-current) --repo Significant-Gravitas/AutoGPT --json title --jq '.[0].title' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | head -c 50)\nRESULTS_DIR=\"$REPO_ROOT/test-results/PR-${PR_NUMBER}-${PR_TITLE}\"\nmkdir -p $RESULTS_DIR\n```\n\n**Test user credentials** — required to log into the UI or call authenticated APIs.\n\nNEVER hardcode these in this SKILL, a PR comment, a screenshot, or any committed file. Sources, in priority order:\n\n1. **Env vars** (CI / preconfigured local shell): `$PR_TEST_USER_EMAIL` + `$PR_TEST_USER_PASSWORD`. If both are set, use them.\n2. **Interactive prompt** (everything else, including dev-preview runs): if the env vars are not set, ASK the user at the start of the run — e.g. \"I need test-user credentials for this run; paste the email and password.\" Hold them in shell vars only for the duration of the run. Do not echo, log, or write them to disk.\n\nAcquire the variables — env first, prompt if missing — and only then lock them in:\n\n```bash\n# 1. Prefer env vars (CI / preconfigured shell). Prompt only for the\n#    specific var that is unset so an already-exported credential is\n#    not overwritten by the prompt when only the other one is missing.\nif [ -z \"${PR_TEST_USER_EMAIL:-}\" ] || [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n  echo \"Test user credentials required for this run.\"\n  if [ -z \"${PR_TEST_USER_EMAIL:-}\" ]; then\n    read -r -p   \"Email:    \" PR_TEST_USER_EMAIL\n  fi\n  if [ -z \"${PR_TEST_USER_PASSWORD:-}\" ]; then\n    read -r -s -p \"Password: \" PR_TEST_USER_PASSWORD\n    echo\n  fi\n  export PR_TEST_USER_EMAIL PR_TEST_USER_PASSWORD\nfi\n\n# 2. Lock them in — fail loudly if either is STILL unset (e.g. the user\n#    pressed Enter on an empty prompt). The error message names the var so\n#    the agent / operator knows what to fix.\n: \"${PR_TEST_USER_EMAIL:?PR_TEST_USER_EMAIL is empty after env+prompt — supply a value before re-running}\"\n: \"${PR_TEST_USER_PASSWORD:?PR_TEST_USER_PASSWORD is empty after env+prompt — supply a value before re-running}\"\n```\n\nFor **local docker-compose** runs, a fresh dev user is created on first call to the signup snippet below. For **dev-preview** runs, the test user lives in the project's hosted auth backend — ask the user for the current valid credentials each session (the previously-shared `test@test.com` test account was disabled on 2026-05-23 after its credentials leaked into this very SKILL — do NOT re-introduce a default). **`PR_TEST_USER_PASSWORD` should always be a throwaway/test-only credential, never a real account's password** — the auth requests in 3h go over plain HTTP on `localhost:3000` for local runs, which has no transport encryption. If a dev-preview run's target isn't on `localhost`, confirm it's HTTPS before sending credentials to it.\n\n## Step 1: Understand the PR\n\nBefore testing, understand what changed:\n\n```bash\ncd $WORKTREE_PATH\n\n# Read PR description to understand the WHY\ngh pr view {N} --json body --jq '.body'\n\ngit log --oneline dev..HEAD | head -20\ngit diff dev --stat\n```\n\nRead the PR description (Why / What / How) and changed files to understand:\n0. **Why** does this PR exist? What problem does it solve?\n1. **What** feature/fix does this PR implement?\n2. **How** does it work? What's the approach?\n3. What components are affected? (backend, frontend, copilot, executor, etc.)\n4. What are the key user-facing behaviors to test?\n\n## Step 2: Write test scenarios\n\nBased on the PR analysis, write a test plan to `$RESULTS_DIR/test-plan.md`:\n\n```markdown\n# Test Plan: PR #{N} — {title}\n\n## Scenarios\n1. [Scenario name] — [what to verify]\n2. ...\n\n## API Tests (if applicable)\n1. [Endpoint] — [expected behavior]\n   - Before state: [what to check before]\n   - After state: [what to verify changed]\n\n## UI Tests (if applicable)\n1. [Page/component] — [interaction to test]\n   - Screenshot before: [what to capture]\n   - Screenshot after: [what to capture]\n\n## Negative Tests (REQUIRED — at least one per feature)\n1. [What should NOT happen] — [how to trigger it]\n   - Expected error: [what error message/code]\n   - State unchanged: [what to verify did NOT change]\n```\n\n**Be critical** — include edge cases, error paths, and security checks. Every scenario MUST specify what screenshots to take and what state to verify.\n\n## Step 3.0: Claim the testing lock (coordinate parallel agents)\n\nMultiple worktrees share the same host — Docker infra (postgres, redis, clamav), app ports (3000/8006/…), and the test user. Two agents running `/pr-test` concurrently will corrupt each other's state (connection-pool exhaustion, port binds failing silently, cross-test assertions). Use the root-worktree lock file to take turns.\n\n### Lock file contract\n\nPath (**always** the root worktree so all siblings see it): `$REPO_ROOT/.ign.testing.lock`\n\nBody (one `key=value` per line):\n```\nholder=<pr-XXXXX-purpose>\npid=<pid-or-\"self\">\nstarted=<iso8601>\nheartbeat=<iso8601, updated every ~2 min>\nworktree=<full path>\nbranch=<branch name>\nintent=<one-line description + rough duration>\n```\n\n### Claim\n\n```bash\nLOCK=$REPO_ROOT/.ign.testing.lock\nNOW=$(date -u +%Y-%m-%dT%H:%MZ)\nSTALE_AFTER_MIN=5\n\nif [ -f \"$LOCK\" ]; then\n  HB=$(grep '^heartbeat=' \"$LOCK\" | cut -d= -f2)\n  HB_EPOCH=$(date -j -f '%Y-%m-%dT%H:%MZ' \"$HB\" +%s 2>/dev/null || date -d \"$HB\" +%s 2>/dev/null || echo 0)\n  AGE_MIN=$(( ( $(date -u +%s) - HB_EPOCH ) / 60 ))\n  if [ \"$AGE_MIN\" -gt \"$STALE_AFTER_MIN\" ]; then\n    echo \"WARN: stale lock (${AGE_MIN}m old) — reclaiming\"\n    cat \"$LOCK\" | sed 's/^/  stale: /'\n  else\n    echo \"Another agent holds the lock:\"; cat \"$LOCK\"\n    echo \"Wait until released or resume after $((STALE_AFTER_MIN - AGE_MIN))m.\"\n    exit 1\n  fi\nfi\n\ncat > \"$LOCK\" <<EOF\nholder=pr-${PR_NUMBER}-e2e\npid=self\nstarted=$NOW\nheartbeat=$NOW\nworktree=$WORKTREE_PATH\nbranch=$(cd $WORKTREE_PATH && git branch --show-current)\nintent=E2E test PR #${PR_NUMBER}, native mode, ~60min\nEOF\necho \"Lock claimed\"\n```\n\n### Heartbeat (MUST run in background during the whole test)\n\nWithout a heartbeat a crashed agent keeps the lock forever. Run this as a background process right after claim:\n\n```bash\n(while true; do\n   sleep 120\n   [ -f \"$LOCK\" ] || exit 0   # lock released → exit heartbeat\n   perl -i -pe \"s/^heartbeat=.*/heartbeat=$(date -u +%Y-%m-%dT%H:%MZ)/\" \"$LOCK\"\n done) &\nHEARTBEAT_PID=$!\necho \"$HEARTBEAT_PID\" > /tmp/pr-test-heartbeat.pid\n```\n\n### Release (always — even on failure)\n\n```bash\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\nUse a `trap` so release runs even on `exit 1`:\n```bash\ntrap 'kill \"$HEARTBEAT_PID\" 2>/dev/null; rm -f \"$LOCK\"' EXIT INT TERM\n```\n\n### **Release the lock AS SOON AS the test run is done**\n\nThe lock guards **test execution**, not **app lifecycle**. Once Step 5 (record results) and Step 6 (post PR comment) are complete, release the lock IMMEDIATELY — even if:\n\n- The native `poetry run app` / `pnpm dev` processes are still running so the user can keep poking at the app manually.\n- You're leaving docker containers up.\n- You're tailing logs for a minute or two.\n\nKeeping the lock held past the test run is the single most common way `/pr-test` stalls other agents. **The app staying up is orthogonal to the lock; don't conflate them.** Sibling worktrees running their own `/pr-test` will kill the stray processes and free the ports themselves (Step 3c/3e-native handle that) — they just need the lock file gone.\n\nConcretely, the sequence at the end of every `/pr-test` run (success or failure) is:\n\n```bash\n# 1. Write the final report + post PR comment — done above in Step 5/6.\n# 2. Release the lock right now, even if the app is still up.\nkill \"$HEARTBEAT_PID\" 2>/dev/null\nrm -f \"$LOCK\" /tmp/pr-test-heartbeat.pid\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] released lock (app may still be running)\" \\\n    >> $REPO_ROOT/.ign.testing.log\n# 3. Optionally leave the app running and note it so the user knows:\necho \"Native stack still running on :3000 / :8006 for manual poking. Kill with:\"\necho \"  pkill -9 -f 'poetry run app'; pkill -9 -f 'next-server|next dev'\"\n```\n\nIf a sibling agent's `/pr-test` needs to take over, it'll do the kill+rebuild dance from Step 3c/3e-native on its own — your only job is to not hold the lock file past the end of your test.\n\n### Shared status log\n\n`$REPO_ROOT/.ign.testing.log` is an append-only channel any agent can read/write. Use it for \"I'm waiting\", \"I'm done, resources free\", or post-run notes:\n```bash\necho \"$(date -u +%Y-%m-%dT%H:%MZ) [pr-${PR_NUMBER}] <message>\" \\\n    >> $REPO_ROOT/.ign.testing.log\n```\n\n## Step 3: Environment setup\n\n### 3a. Copy .env files from the root worktree\n\nThe root worktree (`$REPO_ROOT`) has the canonical `.env` files with all API keys. Copy them to the target worktree:\n\n```bash\n# CRITICAL: .env files are NOT checked into git. They must be copied manually.\ncp $REPO_ROOT/autogpt_platform/.env $PLATFORM_DIR/.env\ncp $REPO_ROOT/autogpt_platform/backend/.env $BACKEND_DIR/.env\ncp $REPO_ROOT/autogpt_platform/frontend/.env $FRONTEND_DIR/.env\n```\n\n**A copy from the root worktree is no longer sufficient on a recent `dev`** —\nauth moved from Supabase to Better Auth (see 3h) and two vars are easy to\nmiss because nothing fails loudly without them, it just 401s later:\n\n- `$BACKEND_DIR/.env` needs `JWT_JWKS_URL` — the Better Auth JWKS endpoint the\n  backend verifies tokens against. The `localhost:3000` value below is for\n  **native mode only**. In docker mode it's harmless to have it in `.env`\n  because `docker-compose.platform.yml` overrides it with the\n  Compose-reachable `http://frontend:3000/api/auth/jwks` — but if you ever run\n  the backend against this `.env` value directly (bypassing Compose), a\n  `localhost` value inside a container resolves to itself, not the frontend,\n  and every call 401s with no other symptom.\n- `$FRONTEND_DIR/.env` needs its **own** `DATABASE_URL` — Better Auth runs\n  inside the Next.js app and talks to Postgres directly, it does not go\n  through the backend. **Derive it from `$BACKEND_DIR/.env`'s `DB_USER` /\n  `DB_PASS` / `DB_PORT` / `DB_NAME` rather than copying\n  `frontend/.env.default`'s placeholder verbatim** — the placeholder happens\n  to match the stock local defaults, but if `backend/.env`'s `DB_PASS` was\n  ever customized (rotated secret, non-default port), copying the placeholder\n  silently points Better Auth at the wrong database instead of the one the\n  rest of the stack actually uses.\n\n```bash\n# [ -n ... ], not grep -q alone — a present-but-empty JWT_JWKS_URL= would\n# otherwise be treated as \"already set\" and skip the fallback, leaving the\n# backend without a JWKS endpoint to verify tokens against.\n[ -n \"$(grep '^JWT_JWKS_URL=' $BACKEND_DIR/.env | cut -d= -f2-)\" ] || echo \"JWT_JWKS_URL=http://localhost:3000/api/auth/jwks\" >> $BACKEND_DIR/.env  # native mode only — see note above\n\nif [ -n \"$(grep '^DATABASE_URL=' $FRONTEND_DIR/.env | cut -d= -f2-)\" ]; then\n  # grep -q alone matches a present-but-empty DATABASE_URL= too, which would\n  # otherwise skip derivation and leave Better Auth pointed at nothing.\n  echo \"Frontend DATABASE_URL: already set (not touching it)\"\nelse\n  # cut -f2 (not -f2-) truncates any value containing '=' (base64 secrets do); -f2- keeps the rest.\n  DB_USER=$(grep '^DB_USER=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PASS=$(grep '^DB_PASS=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_PORT=$(grep '^DB_PORT=' $BACKEND_DIR/.env | cut -d= -f2-)\n  DB_NAME=$(grep '^DB_NAME=' $BACKEND_DIR/.env | cut -d= -f2-)\n  : \"${DB_USER:?}\" \"${DB_PASS:?}\" \"${DB_PORT:?}\" \"${DB_NAME:?}\"  # fail loudly, not with a silently-empty URL\n  # Percent-encode user/pass — a raw '@', '#', '?', '%', or ':' in either would\n  # otherwise be misparsed as URL structure instead of credential content.\n  # Via env vars, not `jq --arg`, which would put DB_PASS in the process arglist.\n  DB_USER_ENC=$(DB_USER_VAL=\"$DB_USER\" jq -rn '$ENV.DB_USER_VAL|@uri')\n  DB_PASS_ENC=$(DB_PASS_VAL=\"$DB_PASS\" jq -rn '$ENV.DB_PASS_VAL|@uri')\n  echo \"DATABASE_URL=postgresql://${DB_USER_ENC}:${DB_PASS_ENC}@localhost:${DB_PORT}/${DB_NAME}\" >> $FRONTEND_DIR/.env\n  # Reconstructed, not regex-redacted — a password containing '@' would otherwise\n  # leak its tail past a naive \"redact up to the first @\" pattern.\n  echo \"Frontend DATABASE_URL: postgresql://${DB_USER_ENC}:***@localhost:${DB_PORT}/${DB_NAME}\"\nfi\n```\n\n### 3b. Configure copilot authentication\n\nThe copilot needs an LLM API to function. Two approaches (try subscription first):\n\n#### Option 1: Subscription mode (preferred — uses your Claude Max/Pro subscription)\n\nThe `claude_agent_sdk` Python package **bundles its own Claude CLI binary** — no need to install `@anthropic-ai/claude-code` via npm. The backend auto-provisions credentials from environment variables on startup.\n\nRun the helper script to extract tokens from your host and auto-update `backend/.env` (works on macOS, Linux, and Windows/WSL):\n\n```bash\n# Extracts OAuth tokens and writes CLAUDE_CODE_OAUTH_TOKEN + CLAUDE_CODE_REFRESH_TOKEN into .env\nbash $BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env\n```\n\n**How it works:** The script reads the OAuth token from:\n- **macOS**: system keychain (`\"Claude Code-credentials\"`)\n- **Linux/WSL**: `~/.claude/.credentials.json`\n- **Windows**: `%APPDATA%/claude/.credentials.json`\n\nIt sets `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_CODE_REFRESH_TOKEN`, and `CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true` in the `.env` file. On container startup, the backend auto-provisions `~/.claude/.credentials.json` inside the container from these env vars. The SDK's bundled CLI then authenticates using that file. No `claude login`, no npm install needed.\n\n**Note:** The OAuth token expires (~24h). If copilot returns auth errors, re-run the script and restart: `$BACKEND_DIR/scripts/refresh_claude_token.sh --env-file $BACKEND_DIR/.env && docker compose up -d copilot_executor`\n\n#### Option 2: OpenRouter API key mode (fallback)\n\nIf subscription mode doesn't work, switch to API key mode using OpenRouter:\n\n```bash\n# In $BACKEND_DIR/.env, ensure these are set:\nCHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false\nCHAT_API_KEY=<value of OPEN_ROUTER_API_KEY from the same .env>\nCHAT_BASE_URL=https://openrouter.ai/api/v1\nCHAT_USE_CLAUDE_AGENT_SDK=true\n```\n\nUse `sed` to update these values:\n```bash\nORKEY=$(grep \"^OPEN_ROUTER_API_KEY=\" $BACKEND_DIR/.env | cut -d= -f2)\n[ -n \"$ORKEY\" ] || { echo \"ERROR: OPEN_ROUTER_API_KEY is missing in $BACKEND_DIR/.env\"; exit 1; }\nperl -i -pe 's/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true/CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=false/' $BACKEND_DIR/.env\n# Add or update CHAT_API_KEY and CHAT_BASE_URL\ngrep -q \"^CHAT_API_KEY=\" $BACKEND_DIR/.env && perl -i -pe \"s|^CHAT_API_KEY=.*|CHAT_API_KEY=$ORKEY|\" $BACKEND_DIR/.env || echo \"CHAT_API_KEY=$ORKEY\" >> $BACKEND_DIR/.env\ngrep -q \"^CHAT_BASE_URL=\" $BACKEND_DIR/.env && perl -i -pe 's|^CHAT_BASE_URL=.*|CHAT_BASE_URL=https://openrouter.ai/api/v1|' $BACKEND_DIR/.env || echo \"CHAT_BASE_URL=https://openrouter.ai/api/v1\" >> $BACKEND_DIR/.env\n```\n\n### 3c. Stop conflicting containers\n\n```bash\n# Stop any running app containers (keep infra: supabase, redis, rabbitmq, clamav)\ndocker ps --format \"{{.Names}}\" | grep -E \"rest_server|executor|copilot|websocket|database_manager|scheduler|notification|frontend|migrate\" | while read name; do\n  docker stop \"$name\" 2>/dev/null\ndone\n```\n\n**Native mode also:** when running the app natively (see 3e-native), kill any stray host processes and free the app ports before starting — otherwise `poetry run app` and `pnpm dev` will fail to bind.\n\n**Kill by port, not by broad process pattern.** A pattern-based\n`pkill -f \"python.*backend\"` (or anything matching by worktree cwd) is too\ncoarse on a host running several worktrees — it has taken out the frontend\nand a mock server sitting on other ports along with the intended backend\nprocess. Target the pid actually holding each port instead — this only kills\nwhoever is bound to that specific port, which is narrower than a pattern\nmatch, but **it is not worktree isolation**: if a sibling worktree's own dev\nserver happens to be using one of these ports (e.g. its frontend also on\n:3000), this kills that too. `lsof` tells you who holds the port, not who\nowns it — a `docker-proxy` pid there means a compose stack owns it.\n\n```bash\n# Free app ports one at a time — errors per port are ignored (port may simply\n# be unused). `xargs -r` is GNU-only (macOS xargs rejects -r); the `[ -n ]`\n# guard below is the portable equivalent.\nfor port in 3000 8006 8001 8002 8005 8008; do\n  pids=$(lsof -ti :$port -sTCP:LISTEN 2>/dev/null)\n  [ -n \"$pids\" ] && kill -9 $pids 2>/dev/null || true\ndone\n```\n\n### 3e-native. Run the app natively (PREFERRED for iterative dev)\n\nNative mode runs infra (postgres, supabase, redis, rabbitmq, clamav) in docker but runs the backend and frontend directly on the host. This avoids the 3-8 minute `docker compose build` cycle on every backend change — code edits are picked up on process restart (seconds) instead of a full image rebuild.\n\n**When to prefer native mode (default for this skill):**\n- Iterative dev/debug loops where you're editing backend or frontend code between test runs\n- Any PR that touches Python/TS source but not Dockerfiles, compose config, or infra images\n- Fast repro of a failing scenario — restart `poetry run app` in a couple of seconds\n\n**When to prefer docker mode (3e fallback):**\n- Testing changes to `Dockerfile`, `docker-compose.yml`, or base images\n- Production-parity smoke tests (exact container env, networking, volumes)\n- CI-equivalent runs where you need the exact image that'll ship\n\n**Note on 3b (copilot auth):** no npm install anywhere. `poetry install` pulls in `claude_agent_sdk`, which ships its own Claude CLI binary — available on `PATH` whenever you run commands via `poetry run` (native) OR whenever the copilot_executor container is built from its Poetry lockfile (docker). The OAuth token extraction still applies (same `refresh_claude_token.sh` call).\n\n**Preamble:** before starting native, run the kill-stray + free-ports block from 3c's \"Native mode also\" subsection.\n\n**1. Start infra only (one-time per session):**\n\n```bash\ncd $PLATFORM_DIR && docker compose --profile local up deps --detach --remove-orphans --build\n```\n\nThis brings up postgres/supabase/redis/rabbitmq/clamav and skips all app services.\n\n**2. Start the backend natively:**\n\n```bash\ncd $BACKEND_DIR && (poetry run app 2>&1 | tee .ign.application.logs) &\n```\n\n`poetry run app` spawns **all** app subprocesses — `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, `database_manager` — inside ONE parent process. No separate containers, no separate terminals. The `.ign.application.logs` prefix is already gitignored.\n\n**3. Wait for the backend on :8006 BEFORE starting the frontend.** This ordering matters — the frontend's `pnpm dev` startup invokes `generate-api-queries`, which fetches `/openapi.json` from the backend. If the backend isn't listening yet, `pnpm dev` fails immediately.\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8006/docs 2>/dev/null)\" = \"200\" ]; then\n    echo \"Backend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\n**4. Start the frontend natively:**\n\n```bash\ncd $FRONTEND_DIR && (pnpm dev 2>&1 | tee .ign.frontend.logs) &\n```\n\n**5. Wait for the frontend on :3000:**\n\n```bash\nfor i in $(seq 1 60); do\n  if [ \"$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000 2>/dev/null)\" = \"200\" ]; then\n    echo \"Frontend ready\"\n    break\n  fi\n  sleep 2\ndone\n```\n\nOnce both are up, skip 3e/3f and go straight to **3g/3h** (feature flags / test user creation).\n\n### 3e. Build and start (docker — fallback)\n\n```bash\ncd $PLATFORM_DIR && docker compose build --no-cache 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker build failed\"; exit 1; fi\n\ncd $PLATFORM_DIR && docker compose up -d 2>&1 | tail -20\nif [ ${PIPESTATUS[0]} -ne 0 ]; then echo \"ERROR: Docker compose up failed\"; exit 1; fi\n```\n\n**Note:** If the container appears to be running old code (e.g. missing PR changes), use `docker compose build --no-cache` to force a full rebuild. Docker BuildKit may sometimes reuse cached `COPY` layers from a previous build on a different branch.\n\n**Expected time: 3-8 minutes** for build, 5-10 minutes with `--no-cache`.\n\n### 3f. Wait for services to be ready\n\n```bash\n# Poll until backend and frontend respond\nfor i in $(seq 1 60); do\n  BACKEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8006/docs 2>/dev/null)\n  FRONTEND=$(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000 2>/dev/null)\n  if [ \"$BACKEND\" = \"200\" ] && [ \"$FRONTEND\" = \"200\" ]; then\n    echo \"Services ready\"\n    break\n  fi\n  sleep 5\ndone\n```\n\n\n### 3h. Create test user and get auth token\n\nThe platform moved off Supabase auth to Better Auth, embedded in the\nNext.js app at `/api/auth/*`. Signup and sign-in both go through the frontend\nnow, not Kong on :8000 — and `/api/auth/token` mints a backend-API JWT from a\n**session cookie**, it does not accept credentials directly, so sign-in has to\nhappen first to get that cookie.\n\nBetter Auth's default minimum password length is **12 characters** — shorter\nvalues fail signup with `PASSWORD_TOO_SHORT` and every step below degrades\nsilently into an empty token unless you check for it.\n\n```bash\nCOOKIE_JAR=$(mktemp)\ntrap 'rm -f \"$COOKIE_JAR\"' EXIT  # cleans up on early exit too, not just the happy path\n# Via env vars, not `jq --arg`, which would put the password in the process arglist.\nAUTH_PAYLOAD=$(PR_TEST_USER_EMAIL=\"$PR_TEST_USER_EMAIL\" PR_TEST_USER_PASSWORD=\"$PR_TEST_USER_PASSWORD\" \\\n  jq -nc '{email:$ENV.PR_TEST_USER_EMAIL,password:$ENV.PR_TEST_USER_PASSWORD,name:\"PR Test User\"}')\n\n# Signup (idempotent — a real error body means \"already exists\" only if you\n# check; -d passes the payload as an argument, which leaks the password into\n# process listings, so pipe it through stdin with --data-binary @- instead.\n# --noproxy guards against an inherited proxy env var routing the password\n# through a proxy. --max-time bounds it — without one, a server that accepts\n# the connection but never responds hangs setup indefinitely instead of\n# reaching the empty-$TOKEN failure check below).\nSIGNUP_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -X POST 'http://localhost:3000/api/auth/sign-up/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNUP_RESULT\" | grep -qi '\"code\"' && echo \"Signup: $SIGNUP_RESULT\"  # log it — \"already exists\" and \"password too short\" look identical downstream otherwise\n\n# Sign in — sets the better-auth.session_token cookie in $COOKIE_JAR.\n# Capture the body: a failure here (e.g. account exists with a different\n# password) otherwise only shows up as an empty $TOKEN with no explanation.\nSIGNIN_RESULT=$(printf '%s' \"$AUTH_PAYLOAD\" | curl -s --max-time 15 --noproxy localhost,127.0.0.1,::1 -c \"$COOKIE_JAR\" -X POST 'http://localhost:3000/api/auth/sign-in/email' \\\n  -H 'Content-Type: application/json' --data-binary @-)\necho \"$SIGNIN_RESULT\" | grep -qi '\"code\"' && echo \"Sign-in: $SIGNIN_RESULT\"\n\n# Mint a backend-API JWT from the session cookie\nTOKEN=$(curl -s -b \"$COOKIE_JAR\" 'http://localhost:3000/api/auth/token' | jq -r '.token // \"\"')\n[ -n \"$TOKEN\" ] || { echo \"ERROR: auth setup failed — TOKEN is empty. Check password length (min 12 chars), AUTH_ALLOW_NEW_ACCOUNTS, AUTH_SIGNUP_ALLOWLIST, and rate limiting on /api/auth/*.\"; exit 1; }\n```\n\n**Use this token for ALL API calls:**\n```bash\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/...\n```\n\n### 3i. Disable onboarding for test user\n\nThe frontend redirects to `/onboarding` when the `ONBOARDING_COMPLETE` step is not in `completedSteps`.\nMark it complete via the backend API so every browser test lands on the real feature UI:\n\n```bash\nONBOARDING_RESULT=$(curl -s --max-time 30 -X POST \\\n  \"http://localhost:8006/api/onboarding/step?step=ONBOARDING_COMPLETE\" \\\n  -H \"Authorization: Bearer $TOKEN\")\necho \"Onboarding bypass: $ONBOARDING_RESULT\"\n\n# Verify it took effect\nONBOARDING_STATUS=$(curl -s --max-time 30 \\\n  \"http://localhost:8006/api/onboarding/completed\" \\\n  -H \"Authorization: Bearer $TOKEN\" | jq -r '.is_completed')\necho \"Onboarding completed: $ONBOARDING_STATUS\"\nif [ \"$ONBOARDING_STATUS\" != \"true\" ]; then\n  echo \"ERROR: onboarding bypass failed — browser tests will hit /onboarding instead of the target feature. Investigate before proceeding.\"\n  exit 1\nfi\n```\n\n## Step 4: Run tests\n\n### Service ports reference\n\n| Service | Port | URL |\n|---------|------|-----|\n| Frontend | 3000 | http://localhost:3000 |\n| Backend REST | 8006 | http://localhost:8006 |\n| Supabase Auth (via Kong) | 8000 | http://localhost:8000 |\n| Executor (metrics only) | 8002 | http://localhost:8002 in native mode; not published by Docker Compose |\n| Copilot Executor (metrics only) | 8008 | http://localhost:8008 in native mode; not published by Docker Compose |\n| WebSocket | 8001 | http://localhost:8001 |\n| Database Manager | 8005 | http://localhost:8005 in native mode; not published by Docker Compose. Every method needs `Authorization: Bearer <internal key>` (`backend/util/service_auth.py`) |\n| Redis | 6379 | localhost:6379 |\n| RabbitMQ | 5672 | localhost:5672 |\n\n### API testing\n\nUse `curl` with the auth token for backend API tests. **For EVERY API call that changes state, record before/after values:**\n\n```bash\n# Example: List agents\ncurl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/graphs | jq . | head -20\n\n# Example: Create an agent\ncurl -s -X POST http://localhost:8006/api/graphs \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{...}' | jq .\n\n# Example: Run an agent\ncurl -s -X POST \"http://localhost:8006/api/graphs/{graph_id}/execute\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"data\": {...}}'\n\n# Example: Get execution results\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:8006/api/graphs/{graph_id}/executions/{exec_id}\" | jq .\n```\n\n**State verification pattern (use for EVERY state-changing API call):**\n```bash\n# 1. Record BEFORE state\nBEFORE_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"BEFORE: $BEFORE_STATE\"\n\n# 2. Perform the action\nACTION_RESULT=$(curl -s -X POST ... | jq .)\necho \"ACTION RESULT: $ACTION_RESULT\"\n\n# 3. Record AFTER state\nAFTER_STATE=$(curl -s -H \"Authorization: Bearer $TOKEN\" http://localhost:8006/api/{resource} | jq '{relevant_fields}')\necho \"AFTER: $AFTER_STATE\"\n\n# 4. Log the comparison\necho \"=== STATE CHANGE VERIFICATION ===\"\necho \"Before: $BEFORE_STATE\"\necho \"After: $AFTER_STATE\"\necho \"Expected change: {describe what should have changed}\"\n```\n\n### Mock-provider pattern (deterministic, $0)\n\nFor timeout/latency/error-handling behavior that would otherwise need a real\nLLM call, point the OpenAI SDK at a local mock instead — it honours\n`OPENAI_BASE_URL`, so a small local Responses-API server can stand in for the\nprovider while everything downstream (executor, credentials, billing) still\nruns for real. This proved out 4/4 test items at $0 in this run. **This\ncovers LLM blocks (`providers.py`) and the Codex block** — the copilot's own\nLLM calls go through `backend/util/clients.py`, which passes `base_url`\nexplicitly and does not read `OPENAI_BASE_URL`, so this trick doesn't reach\ncopilot chat.\n\n```bash\n# In $BACKEND_DIR/.env, point the OpenAI provider at a local mock server\n# that implements the subset of the Responses API you need (e.g. delayed\n# responses to test timeout handling, or a 500 to test error surfacing).\n# Restart the backend after changing this — it's read at startup.\nOPENAI_BASE_URL=http://localhost:{mock_port}/v1\n```\n\n**Use a throwaway/dummy provider credential with the mock, never the system\ncredential** — only the transport is faked, so the credential lookup still\nruns for real and whatever key you configure gets sent as a header to your\nlocal mock server. A dummy key also means the mock server's logs (which may\nend up pasted into a PR comment) can't leak a real one. Aside from the\ncredential, this is safe to use for anything that isn't itself testing model\n*output* quality.\n\nIn **docker mode**, `localhost` inside the backend container isn't reachable\nfrom your host-side mock server — point `OPENAI_BASE_URL` at a\nCompose-reachable hostname instead (or `host.docker.internal` with a\n`host-gateway` entry), and set it before `docker compose up` or restart the\naffected services after changing `$BACKEND_DIR/.env`.\n\nKeep the mock server's own response timeout short — the OpenAI SDK's default\nclient timeout is 600s, so a hung mock stalls every LLM-backed block for that\nlong instead of failing fast.\n\n### Browser testing with agent-browser\n\nPrimary tool — use this wherever `agent-browser` is installed:\n\n```bash\n# Close any existing session\nagent-browser close 2>/dev/null || true\n\n# Use --session-name to persist cookies across navigations\n# This means login only needs to happen once per test session\nagent-browser --session-name pr-test open 'http://localhost:3000/login' --timeout 15000\n\n# Get interactive elements\nagent-browser --session-name pr-test snapshot | grep \"textbox\\|button\"\n\n# Login (read creds from env — set PR_TEST_USER_EMAIL / PR_TEST_USER_PASSWORD or ask the user)\nagent-browser --session-name pr-test fill {email_ref} \"$PR_TEST_USER_EMAIL\"\nagent-browser --session-name pr-test fill {password_ref} \"$PR_TEST_USER_PASSWORD\"\nagent-browser --session-name pr-test click {login_button_ref}\nsleep 5\n\n# Dismiss cookie banner if present\nagent-browser --session-name pr-test click 'text=Accept All' 2>/dev/null || true\n\n# Navigate — cookies are preserved so login persists\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n\n# Take screenshot\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/01-page.png\n\n# Interact with elements\nagent-browser --session-name pr-test fill {ref} \"text\"\nagent-browser --session-name pr-test press \"Enter\"\nagent-browser --session-name pr-test click {ref}\nagent-browser --session-name pr-test click 'text=Button Text'\n\n# Read page content\nagent-browser --session-name pr-test snapshot | grep \"text:\"\n```\n\n**Key pages:**\n- `/copilot` — CoPilot chat (for testing copilot features)\n- `/build` — Agent builder (for testing block/node features)\n- `/build?flowID={id}` — Specific agent in builder\n- `/library` — Agent library (for testing listing/import features)\n- `/library/agents/{id}` — Agent detail with run history\n- `/marketplace` — Marketplace\n\n**Fallback — if `agent-browser` isn't installed on the host, don't let `npx`\ndownload it.** Use `@playwright/test` instead — the package is already in the\nfrontend's `node_modules`, but **the Chromium binary itself is not**;\nPlaywright caches browser binaries separately (`~/.cache/ms-playwright/` by\ndefault) and nothing installs them automatically. Run\n`pnpm exec playwright install chromium` once per host if `chromium.launch()`\nfails looking for an executable, then use it for finer control over timing\n(`waitForSelector`, explicit timeouts) than agent-browser's CLI gives you:\n\n```bash\ncd $FRONTEND_DIR  # required — @playwright/test is only declared here, not at the repo root\n# Check the installer's own exit status, not grep's — piping through grep to\n# drop blank lines would otherwise swallow a real install failure and let\n# chromium.launch() below fail later with a much less clear error.\npnpm exec playwright install chromium\n[ $? -eq 0 ] || { echo \"ERROR: playwright install chromium failed\"; exit 1; }\n\nnode -e \"\nconst { chromium } = require('@playwright/test');\n(async () => {\n  const browser = await chromium.launch();\n  try {\n    const page = await browser.newPage();\n    await page.goto('http://localhost:3000/login');\n    await page.screenshot({ path: '$RESULTS_DIR/01-login.png' });\n  } finally {\n    await browser.close();  // otherwise a goto timeout leaks a headless Chromium process\n  }\n})();\n\"\n```\n\n### Checking logs\n\n**Native mode:** when running via `poetry run app` + `pnpm dev`, all app logs stream to the `.ign.*.logs` files written by the `tee` pipes in 3e-native. `rest_server`, `executor`, `copilot_executor`, `websocket`, `scheduler`, `notification_server`, and `database_manager` are all subprocesses of the single `poetry run app` parent, so their output is interleaved in `.ign.application.logs`.\n\n```bash\n# Backend (all app subprocesses interleaved)\ntail -f $BACKEND_DIR/.ign.application.logs\n\n# Frontend (Next.js dev server)\ntail -f $FRONTEND_DIR/.ign.frontend.logs\n\n# Filter for errors across either log\ngrep -iE \"error|exception|traceback\" $BACKEND_DIR/.ign.application.logs | tail -20\ngrep -iE \"error|exception|traceback\" $FRONTEND_DIR/.ign.frontend.logs | tail -20\n```\n\n**Docker mode:**\n\n```bash\n# Backend REST server\ndocker logs autogpt_platform-rest_server-1 2>&1 | tail -30\n\n# Executor (runs agent graphs)\ndocker logs autogpt_platform-executor-1 2>&1 | tail -30\n\n# Copilot executor (runs copilot chat sessions)\ndocker logs autogpt_platform-copilot_executor-1 2>&1 | tail -30\n\n# Frontend\ndocker logs autogpt_platform-frontend-1 2>&1 | tail -30\n\n# Filter for errors\ndocker logs autogpt_platform-executor-1 2>&1 | grep -i \"error\\|exception\\|traceback\" | tail -20\n```\n\n### Copilot chat testing\n\nThe copilot uses SSE streaming. To test via API:\n\n```bash\n# Create a session\nSESSION_ID=$(curl -s -X POST 'http://localhost:8006/api/chat/sessions' \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}' | jq -r '.id // .session_id // \"\"')\n\n# Stream a message (SSE - will stream chunks)\ncurl -N -X POST \"http://localhost:8006/api/chat/sessions/$SESSION_ID/stream\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"message\": \"Hello, what can you help me with?\"}' \\\n  --max-time 60 2>/dev/null | head -50\n```\n\nOr test via browser (preferred for UI verification):\n```bash\nagent-browser --session-name pr-test open 'http://localhost:3000/copilot' --timeout 10000\n# ... fill chat input and press Enter, wait 20-30s for response\n```\n\n## Step 5: Record results and take screenshots\n\n**Take a screenshot at EVERY significant test step** — before and after interactions, on success, and on failure. This is NON-NEGOTIABLE.\n\n**Required screenshot pattern for each test scenario:**\n```bash\n# BEFORE the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-before.png\n\n# Perform the action...\n\n# AFTER the action\nagent-browser --session-name pr-test screenshot $RESULTS_DIR/{NN}-{scenario}-after.png\n```\n\n**Naming convention:**\n```bash\n# Examples:\n# $RESULTS_DIR/01-login-page-before.png\n# $RESULTS_DIR/02-login-page-after.png\n# $RESULTS_DIR/03-credits-page-before.png\n# $RESULTS_DIR/04-credits-purchase-after.png\n# $RESULTS_DIR/05-negative-insufficient-credits.png\n# $RESULTS_DIR/06-error-state.png\n```\n\n**Minimum requirements:**\n- At least TWO screenshots per test scenario (before + after)\n- At least ONE screenshot for each negative test case showing the error state\n- If a test fails, screenshot the failure state AND any error logs visible in the UI\n\n## Step 6: Show results to user with screenshots\n\n**CRITICAL: After all tests complete, you MUST show every screenshot to the user using the Read tool, with an explanation of what each screenshot shows.** This is the most important part of the test report — the user needs to visually verify the results.\n\nFor each screenshot:\n1. Use the `Read` tool to display the PNG file (Claude can read images)\n2. Write a 1-2 sentence explanation below it describing:\n   - What page/state is being shown\n   - What the screenshot proves (which test scenario it validates)\n   - Any notable details visible in the UI\n\nFormat the output like this:\n\n```markdown\n### Screenshot 1: {descriptive title}\n[Read the PNG file here]\n\n**What it shows:** {1-2 sentence explanation of what this screenshot proves}\n\n---\n```\n\nAfter showing all screenshots, output a **detailed** summary table:\n\n| # | Scenario | Result | API Evidence | Screenshot Evidence |\n|---|----------|--------|-------------|-------------------|\n| 1 | {name} | PASS/FAIL | Before: X, After: Y | 01-before.png, 02-after.png |\n| 2 | ... | ... | ... | ... |\n\n**IMPORTANT:** As you show each screenshot and record test results, persist them in shell variables for Step 7:\n\n```bash\n# Build these variables during Step 6 — they are required by Step 7's script\n# NOTE: declare -A requires Bash 4.0+. This is standard on modern systems (macOS ships zsh\n# but Homebrew bash is 5.x; Linux typically has bash 5.x). If running on Bash <4, use a\n# plain variable with a lookup function instead.\ndeclare -A SCREENSHOT_EXPLANATIONS=(\n  [\"01-login-page.png\"]=\"Shows the login page loaded successfully with SSO options visible.\"\n  [\"02-builder-with-block.png\"]=\"The builder canvas displays the newly added block connected to the trigger.\"\n  # ... one entry per screenshot, using the same explanations you showed the user above\n)\n\nTEST_RESULTS_TABLE=\"| 1 | Login flow | PASS | N/A | 01-login-before.png, 02-login-after.png |\n| 2 | Credits purchase | PASS | Before: 100, After: 95 | 03-credits-before.png, 04-credits-after.png |\n| 3 | Insufficient credits (negative) | PASS | Credits: 0, rejected | 05-insufficient-credits-error.png |\"\n# ... one row per test scenario with actual results\n```\n\n## Step 7: Post test report as PR comment with screenshots\n\nPost the report with `gh pr comment --attach`. Each attached file is uploaded to GitHub's own asset store, and any image the body already references by that path is rewritten to point at the uploaded asset. Screenshots never touch a repo branch.\n\n**This step is MANDATORY. Every test run MUST post a PR comment — with the screenshots attached, or an image-free INCOMPLETE report when attachment is unavailable (see the fallback below). Never nothing.**\n\n**Attachments are permanent.** A `user-attachments` asset stays reachable by anyone holding its URL regardless of repo visibility, and deleting the comment does not reliably revoke it — there is no undo. Look at every screenshot for credentials, tokens, or customer data before this step runs.\n\n**Requires `gh` >= 2.99.0** — `--attach` landed there. Distro packages lag (Fedora's rpm tops out at 2.97.0), so check the version you are actually running before building the body:\n\n```bash\n# Too-old gh is not fatal here: the report still gets posted, just without\n# images, through the same fallback path a failed upload takes (below).\nGH_VERSION=$(gh version 2>/dev/null | head -1 | awk '{print $3}')\n# Numeric compare, not `sort -V`: BSD sort on macOS may lack -V, and an empty\n# substitution would silently read as \"too old\" on a perfectly good gh.\nGH_MAJOR=${GH_VERSION%%.*}; GH_REST=${GH_VERSION#*.}; GH_MINOR=${GH_REST%%.*}\nATTACH_OK=0\nATTACH_SKIP_REASON=\"\"\nif [ \"${GH_MAJOR:-0}\" -gt 2 ] 2>/dev/null || { [ \"${GH_MAJOR:-0}\" -eq 2 ] && [ \"${GH_MINOR:-0}\" -ge 99 ]; } 2>/dev/null; then\n  ATTACH_OK=1\nelse\n  ATTACH_SKIP_REASON=\"gh ${GH_VERSION:-not found} is below 2.99.0 — upgrade gh\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\nfi\n```\n\n**CRITICAL — NEVER post a bare directory link like `https://github.com/.../tree/...`.** Every screenshot MUST appear as `![name](url)` inline in the PR comment so reviewers can see them without clicking any links. After posting, the verification step below counts the rewritten asset URLs against the number of screenshots and exits 1 on any shortfall — the test run is incomplete until this passes.\n\n**CRITICAL — NEVER paste absolute local paths or credentials into the PR comment.** Strings like `/Users/…`, `/home/…`, `C:\\…` are useless to every reviewer except you, and a bearer token or JWT copied from the live stack's evidence is a real leak in a public comment. The build block below runs an egress check on the exact bytes about to be posted and aborts on either. The `./01-shot.png` references the attachments use are relative, not local paths, and are rewritten on upload — they are fine. Keep local paths in `$RESULTS_DIR/test-report.md` for yourself; only copy the *content* they reference (excerpts, test names, log lines) into the PR comment, not the path.\n\nBuild the body with **relative image references**, then attach the same paths:\n\n```bash\nREPO=\"Significant-Gravitas/AutoGPT\"\nMAX_ATTACHMENTS=50   # per gh pr comment invocation\n\n# Fail closed: an empty RESULTS_DIR would glob the PR worktree's PNGs into a public comment.\n# gh matches --attach to the body by exact path, so the cd is required; undone at the end.\n[ -n \"$RESULTS_DIR\" ] && [ -d \"$RESULTS_DIR\" ] || { echo \"ERROR: RESULTS_DIR is unset or not a directory: '$RESULTS_DIR'\"; exit 1; }\nSTEP7_ORIG_DIR=$PWD\ncd \"$RESULTS_DIR\" || exit 1\nNULLGLOB_WAS=$(shopt -p nullglob)   # restore the caller's setting, don't force it off\nshopt -s nullglob\nSCREENSHOT_FILES=(*.png)\n$NULLGLOB_WAS\nif [ ${#SCREENSHOT_FILES[@]} -eq 0 ]; then\n  echo \"ERROR: No screenshots found in $RESULTS_DIR. Test run is incomplete.\"\n  exit 1\nfi\n# Over the cap, post the report without images rather than nothing at all.\nif [ ${#SCREENSHOT_FILES[@]} -gt \"$MAX_ATTACHMENTS\" ]; then\n  ATTACH_SKIP_REASON=\"${#SCREENSHOT_FILES[@]} screenshots exceed the ${MAX_ATTACHMENTS}-attachment limit per comment\"\n  echo \"WARN: ${ATTACH_SKIP_REASON}; posting the report without images.\"\n  ATTACH_OK=0\nfi\n\nIMAGE_MARKDOWN=\"\"\nATTACH_ARGS=()\nfor BASENAME in \"${SCREENSHOT_FILES[@]}\"; do\n  TITLE=$(echo \"${BASENAME%.png}\" | sed 's/^[0-9]*-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')\n  EXPLANATION=\"${SCREENSHOT_EXPLANATIONS[$BASENAME]}\"\n  if [ -z \"$EXPLANATION\" ]; then\n    echo \"ERROR: Missing screenshot explanation for $BASENAME. Add it to SCREENSHOT_EXPLANATIONS in Step\n\n<!-- File: .claude/skills/setup-repo/SKILL.md (Tokens: ~1732 | Category: anthropic-skill) -->\n---\nname: setup-repo\ndescription: Initialize a worktree-based repo layout for parallel development. Creates a main worktree, a reviews worktree for PR reviews, and N numbered work branches. Handles .env creation, dependency installation, and branchlet config. TRIGGER when user asks to set up the repo from scratch, initialize worktrees, bootstrap their dev environment, \"setup repo\", \"setup worktrees\", \"initialize dev environment\", \"set up branches\", or when a freshly cloned repo has no sibling worktrees.\nuser-invocable: true\nargs: \"No arguments — interactive setup via prompts.\"\nmetadata:\n  author: autogpt-team\n  version: \"1.0.0\"\n---\n\n# Repository Setup\n\nThis skill sets up a worktree-based development layout from a freshly cloned repo. It creates:\n- A **main** worktree (the primary checkout)\n- A **reviews** worktree (for PR reviews)\n- **N work branches** (branch1..branchN) for parallel development\n\n## Step 1: Identify the repo\n\nDetermine the repo root and parent directory:\n\n```bash\nROOT=$(git rev-parse --show-toplevel)\nREPO_NAME=$(basename \"$ROOT\")\nPARENT=$(dirname \"$ROOT\")\n```\n\nDetect if the repo is already inside a worktree layout by counting sibling worktrees (not just checking the directory name, which could be anything):\n\n```bash\n# Count worktrees that are siblings (live under $PARENT but aren't $ROOT itself)\nSIBLING_COUNT=$(git worktree list --porcelain 2>/dev/null | grep \"^worktree \" | grep -c \"$PARENT/\" || true)\nif [ \"$SIBLING_COUNT\" -gt 1 ]; then\n  echo \"INFO: Existing worktree layout detected at $PARENT ($SIBLING_COUNT worktrees)\"\n  # Use $ROOT as-is; skip renaming/restructuring\nelse\n  echo \"INFO: Fresh clone detected, proceeding with setup\"\nfi\n```\n\n## Step 2: Ask the user questions\n\nUse AskUserQuestion to gather setup preferences:\n\n1. **How many parallel work branches do you need?** (Options: 4, 8, 16, or custom)\n   - These become `branch1` through `branchN`\n2. **Which branch should be the base?** (Options: origin/master, origin/dev, or custom)\n   - All work branches and reviews will start from this\n\n## Step 3: Fetch and set up branches\n\n```bash\ncd \"$ROOT\"\ngit fetch origin\n\n# Create the reviews branch from base (skip if already exists)\nif git show-ref --verify --quiet refs/heads/reviews; then\n  echo \"INFO: Branch 'reviews' already exists, skipping\"\nelse\n  git branch reviews <base-branch>\nfi\n\n# Create numbered work branches from base (skip if already exists)\nfor i in $(seq 1 \"$COUNT\"); do\n  if git show-ref --verify --quiet \"refs/heads/branch$i\"; then\n    echo \"INFO: Branch 'branch$i' already exists, skipping\"\n  else\n    git branch \"branch$i\" <base-branch>\n  fi\ndone\n```\n\n## Step 4: Create worktrees\n\nCreate worktrees as siblings to the main checkout:\n\n```bash\nif [ -d \"$PARENT/reviews\" ]; then\n  echo \"INFO: Worktree '$PARENT/reviews' already exists, skipping\"\nelse\n  git worktree add \"$PARENT/reviews\" reviews\nfi\n\nfor i in $(seq 1 \"$COUNT\"); do\n  if [ -d \"$PARENT/branch$i\" ]; then\n    echo \"INFO: Worktree '$PARENT/branch$i' already exists, skipping\"\n  else\n    git worktree add \"$PARENT/branch$i\" \"branch$i\"\n  fi\ndone\n```\n\n## Step 5: Set up environment files\n\n**Do NOT assume .env files exist.** For each worktree (including main if needed):\n\n1. Check if `.env` exists in the source worktree for each path\n2. If `.env` exists, copy it\n3. If only `.env.default` or `.env.example` exists, copy that as `.env`\n4. If neither exists, warn the user and list which env files are missing\n\nEnv file locations to check (same as the `/worktree` skill — keep these in sync):\n- `autogpt_platform/.env`\n- `autogpt_platform/backend/.env`\n- `autogpt_platform/frontend/.env`\n\n> **Note:** This env copying logic intentionally mirrors the `/worktree` skill's approach. If you update the path list or fallback logic here, update `/worktree` as well.\n\n```bash\nSOURCE=\"$ROOT\"\nWORKTREES=\"reviews\"\nfor i in $(seq 1 \"$COUNT\"); do WORKTREES=\"$WORKTREES branch$i\"; done\n\nFOUND_ANY_ENV=0\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  for envpath in autogpt_platform autogpt_platform/backend autogpt_platform/frontend; do\n    if [ -f \"$SOURCE/$envpath/.env\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env\" \"$TARGET/$envpath/.env\"\n    elif [ -f \"$SOURCE/$envpath/.env.default\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.default\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.default — you may need to edit it\"\n    elif [ -f \"$SOURCE/$envpath/.env.example\" ]; then\n      FOUND_ANY_ENV=1\n      cp \"$SOURCE/$envpath/.env.example\" \"$TARGET/$envpath/.env\"\n      echo \"NOTE: $wt/$envpath/.env was created from .env.example — you may need to edit it\"\n    else\n      echo \"WARNING: No .env, .env.default, or .env.example found at $SOURCE/$envpath/\"\n    fi\n  done\ndone\n\nif [ \"$FOUND_ANY_ENV\" -eq 0 ]; then\n  echo \"WARNING: No environment files or templates were found in the source worktree.\"\n  # Use AskUserQuestion to confirm: \"Continue setup without env files?\"\n  # If the user declines, stop here and let them set up .env files first.\nfi\n```\n\n## Step 6: Copy branchlet config\n\nCopy `.branchlet.json` from main to each worktree so branchlet can manage sub-worktrees:\n\n```bash\nif [ -f \"$ROOT/.branchlet.json\" ]; then\n  for wt in $WORKTREES; do\n    cp \"$ROOT/.branchlet.json\" \"$PARENT/$wt/.branchlet.json\"\n  done\nfi\n```\n\n## Step 7: Install dependencies\n\nInstall deps in all worktrees. Run these sequentially per worktree:\n\n```bash\nfor wt in $WORKTREES; do\n  TARGET=\"$PARENT/$wt\"\n  echo \"=== Installing deps for $wt ===\"\n  (cd \"$TARGET/autogpt_platform/autogpt_libs\" && poetry install) &&\n  (cd \"$TARGET/autogpt_platform/backend\" && poetry install && poetry run prisma generate) &&\n  (cd \"$TARGET/autogpt_platform/frontend\" && pnpm install) &&\n  echo \"=== Done: $wt ===\" ||\n  echo \"=== FAILED: $wt ===\"\ndone\n```\n\nThis is slow. Run in background if possible and notify when complete.\n\n## Step 8: Verify and report\n\nAfter setup, verify and report to the user:\n\n```bash\ngit worktree list\n```\n\nSummarize:\n- Number of worktrees created\n- Which env files were copied vs created from defaults vs missing\n- Any warnings or errors encountered\n\n## Final directory layout\n\n```\nparent/\n  main/              # Primary checkout (already exists)\n  reviews/           # PR review worktree\n  branch1/           # Work branch 1\n  branch2/           # Work branch 2\n  ...\n  branchN/           # Work branch N\n```\n\n<!-- File: .claude/skills/vercel-react-best-practices/AGENTS.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Vercel-react-best-practices Skill\n\nPath: `.claude/skills/vercel-react-best-practices/AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/AGENTS.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/async-api-routes.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-api-routes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/async-defer-await.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-defer-await.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/async-dependencies.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-dependencies.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/async-parallel.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-parallel.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-parallel.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/bundle-conditional.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md)\n\n<!-- File: .claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Rules Skill\n\nPath: `.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Significant-Gravitas/AutoGPT/HEAD/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md)\n\n<!-- ... +79 more modular skills available via /api/skills/Significant-Gravitas/AutoGPT?path={path} -->\n</yakaai_skills>"}