qaas-python 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qaas_python-0.1.0/.gitignore +15 -0
- qaas_python-0.1.0/ARCHITECTURE.md +469 -0
- qaas_python-0.1.0/BUILD_PLAN.md +306 -0
- qaas_python-0.1.0/CLAUDE.md +218 -0
- qaas_python-0.1.0/LICENSE +21 -0
- qaas_python-0.1.0/PKG-INFO +388 -0
- qaas_python-0.1.0/README.md +358 -0
- qaas_python-0.1.0/config/targets/corvid.yaml +56 -0
- qaas_python-0.1.0/pyproject.toml +79 -0
- qaas_python-0.1.0/src/qaas/adapters/__init__.py +19 -0
- qaas_python-0.1.0/src/qaas/adapters/tracker.py +1350 -0
- qaas_python-0.1.0/src/qaas/adapters/vcs.py +494 -0
- qaas_python-0.1.0/src/qaas/cli.py +1564 -0
- qaas_python-0.1.0/src/qaas/conductor.py +527 -0
- qaas_python-0.1.0/src/qaas/config.py +407 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/arbiter.yaml +19 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/cartographer.yaml +20 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/clerk.yaml +21 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/conduit.yaml +19 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/forge.yaml +22 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/mender.yaml +56 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/proof.yaml +21 -0
- qaas_python-0.1.0/src/qaas/defaults/config/agents/surface.yaml +16 -0
- qaas_python-0.1.0/src/qaas/defaults/config/system.yaml +69 -0
- qaas_python-0.1.0/src/qaas/discover.py +227 -0
- qaas_python-0.1.0/src/qaas/envelope.py +290 -0
- qaas_python-0.1.0/src/qaas/guardrails.py +431 -0
- qaas_python-0.1.0/src/qaas/mcp/__init__.py +0 -0
- qaas_python-0.1.0/src/qaas/mcp/context.py +70 -0
- qaas_python-0.1.0/src/qaas/mcp/contract_diff.py +937 -0
- qaas_python-0.1.0/src/qaas/mcp/defect_memory.py +495 -0
- qaas_python-0.1.0/src/qaas/mcp/env_control.py +905 -0
- qaas_python-0.1.0/src/qaas/mcp/envelope_server.py +463 -0
- qaas_python-0.1.0/src/qaas/mcp/test_runner.py +773 -0
- qaas_python-0.1.0/src/qaas/mcp/tracker.py +412 -0
- qaas_python-0.1.0/src/qaas/mcp/vcs.py +506 -0
- qaas_python-0.1.0/src/qaas/paths.py +317 -0
- qaas_python-0.1.0/src/qaas/plugin/.claude-plugin/plugin.json +9 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/a11y-audit/SKILL.md +34 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/adversarial-review/SKILL.md +120 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/api-surface-extraction/SKILL.md +38 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/authz-matrix-check/SKILL.md +46 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/console-error-triage/SKILL.md +39 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/contract-test-generation/SKILL.md +36 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/dedupe-strategy/SKILL.md +39 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/environment-pinning/SKILL.md +35 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/error-taxonomy/SKILL.md +42 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/exploratory-ui-walk/SKILL.md +46 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/failing-test-authoring/SKILL.md +47 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/flake-detection/SKILL.md +39 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/form-state-probe/SKILL.md +36 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/minimal-diff-discipline/SKILL.md +70 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/openapi-diff/SKILL.md +45 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/ownership-resolution/SKILL.md +31 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/product-task-graph/SKILL.md +35 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/regression-risk-scoring/SKILL.md +59 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/regression-suite-selection/SKILL.md +36 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/repo-cartography/SKILL.md +38 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/repro-minimisation/SKILL.md +41 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/rollback-plan-authoring/SKILL.md +81 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/root-cause-vs-symptom/SKILL.md +67 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/routing-rules/SKILL.md +34 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/severity-rubric/SKILL.md +42 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/test-first-fix/SKILL.md +66 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/test-quality-audit/SKILL.md +58 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/ticket-writer/SKILL.md +40 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/verdict-reporting/SKILL.md +35 -0
- qaas_python-0.1.0/src/qaas/plugin/skills/verification-protocol/SKILL.md +39 -0
- qaas_python-0.1.0/src/qaas/prompts/ARBITER.md +53 -0
- qaas_python-0.1.0/src/qaas/prompts/CARTOGRAPHER.md +46 -0
- qaas_python-0.1.0/src/qaas/prompts/CLERK.md +45 -0
- qaas_python-0.1.0/src/qaas/prompts/CONDUIT.md +44 -0
- qaas_python-0.1.0/src/qaas/prompts/FORGE.md +43 -0
- qaas_python-0.1.0/src/qaas/prompts/MENDER.md +55 -0
- qaas_python-0.1.0/src/qaas/prompts/PROOF.md +41 -0
- qaas_python-0.1.0/src/qaas/prompts/SURFACE.md +46 -0
- qaas_python-0.1.0/src/qaas/prompts/_shared.md +45 -0
- qaas_python-0.1.0/src/qaas/registry.py +465 -0
- qaas_python-0.1.0/src/qaas/runner.py +192 -0
- qaas_python-0.1.0/src/qaas/scorecard.py +425 -0
- qaas_python-0.1.0/src/qaas/sdk_compat.py +52 -0
- qaas_python-0.1.0/src/qaas/store.py +290 -0
- qaas_python-0.1.0/src/qaas/target.py +261 -0
- qaas_python-0.1.0/src/qaas/tasks.py +361 -0
- qaas_python-0.1.0/src/qaas/trace.py +270 -0
- qaas_python-0.1.0/target-app/CODEOWNERS +15 -0
- qaas_python-0.1.0/target-app/api/Dockerfile +17 -0
- qaas_python-0.1.0/target-app/api/app/__init__.py +3 -0
- qaas_python-0.1.0/target-app/api/app/auth.py +113 -0
- qaas_python-0.1.0/target-app/api/app/config.py +38 -0
- qaas_python-0.1.0/target-app/api/app/db.py +36 -0
- qaas_python-0.1.0/target-app/api/app/errors.py +56 -0
- qaas_python-0.1.0/target-app/api/app/main.py +44 -0
- qaas_python-0.1.0/target-app/api/app/models.py +103 -0
- qaas_python-0.1.0/target-app/api/app/routes/__init__.py +5 -0
- qaas_python-0.1.0/target-app/api/app/routes/auth.py +31 -0
- qaas_python-0.1.0/target-app/api/app/routes/invoices.py +37 -0
- qaas_python-0.1.0/target-app/api/app/routes/orders.py +130 -0
- qaas_python-0.1.0/target-app/api/app/routes/stream.py +72 -0
- qaas_python-0.1.0/target-app/api/app/schemas.py +89 -0
- qaas_python-0.1.0/target-app/api/migrations/001_init.sql +61 -0
- qaas_python-0.1.0/target-app/api/pyproject.toml +22 -0
- qaas_python-0.1.0/target-app/api/seed/fixtures.sql +155 -0
- qaas_python-0.1.0/target-app/cross-tenant-order-detail-leak.png +0 -0
- qaas_python-0.1.0/target-app/defects.yaml +361 -0
- qaas_python-0.1.0/target-app/docker-compose.yml +48 -0
- qaas_python-0.1.0/target-app/new-order-validation-wipes-form.png +0 -0
- qaas_python-0.1.0/target-app/openapi.yaml +246 -0
- qaas_python-0.1.0/target-app/order-placed-banner-but-draft-status.png +0 -0
- qaas_python-0.1.0/target-app/place-order-noop-single-item.png +0 -0
- qaas_python-0.1.0/target-app/signout-secondary-button-contrast.png +0 -0
- qaas_python-0.1.0/target-app/web/.gitignore +3 -0
- qaas_python-0.1.0/target-app/web/Dockerfile +14 -0
- qaas_python-0.1.0/target-app/web/dist/assets/index-Cdavr0Eg.css +1 -0
- qaas_python-0.1.0/target-app/web/dist/assets/index-zpKS18v-.js +67 -0
- qaas_python-0.1.0/target-app/web/dist/index.html +13 -0
- qaas_python-0.1.0/target-app/web/index.html +12 -0
- qaas_python-0.1.0/target-app/web/package-lock.json +1792 -0
- qaas_python-0.1.0/target-app/web/package.json +24 -0
- qaas_python-0.1.0/target-app/web/src/api.ts +232 -0
- qaas_python-0.1.0/target-app/web/src/components/Button.tsx +24 -0
- qaas_python-0.1.0/target-app/web/src/components/Layout.tsx +53 -0
- qaas_python-0.1.0/target-app/web/src/components/SearchInput.tsx +24 -0
- qaas_python-0.1.0/target-app/web/src/main.tsx +74 -0
- qaas_python-0.1.0/target-app/web/src/routes/CheckoutReview.tsx +152 -0
- qaas_python-0.1.0/target-app/web/src/routes/Login.tsx +100 -0
- qaas_python-0.1.0/target-app/web/src/routes/NewOrder.tsx +258 -0
- qaas_python-0.1.0/target-app/web/src/routes/OrderDetail.tsx +179 -0
- qaas_python-0.1.0/target-app/web/src/routes/OrdersList.tsx +126 -0
- qaas_python-0.1.0/target-app/web/src/styles.css +430 -0
- qaas_python-0.1.0/target-app/web/tsconfig.json +21 -0
- qaas_python-0.1.0/target-app/web/vite.config.ts +28 -0
- qaas_python-0.1.0/tests/adapters/test_github_vcs.py +461 -0
- qaas_python-0.1.0/tests/adapters/test_jira_tracker.py +1048 -0
- qaas_python-0.1.0/tests/conftest.py +42 -0
- qaas_python-0.1.0/tests/mcp/conftest.py +114 -0
- qaas_python-0.1.0/tests/mcp/test_contract_diff.py +492 -0
- qaas_python-0.1.0/tests/mcp/test_defect_memory.py +230 -0
- qaas_python-0.1.0/tests/mcp/test_env_control.py +309 -0
- qaas_python-0.1.0/tests/mcp/test_test_runner.py +273 -0
- qaas_python-0.1.0/tests/mcp/test_tracker.py +325 -0
- qaas_python-0.1.0/tests/mcp/test_vcs.py +297 -0
- qaas_python-0.1.0/tests/support.py +29 -0
- qaas_python-0.1.0/tests/target_app/test_seeded_defects.py +247 -0
- qaas_python-0.1.0/tests/test_cli.py +196 -0
- qaas_python-0.1.0/tests/test_conductor.py +545 -0
- qaas_python-0.1.0/tests/test_config.py +219 -0
- qaas_python-0.1.0/tests/test_discover.py +115 -0
- qaas_python-0.1.0/tests/test_envelope.py +202 -0
- qaas_python-0.1.0/tests/test_guardrails.py +495 -0
- qaas_python-0.1.0/tests/test_hooks_and_skills.py +216 -0
- qaas_python-0.1.0/tests/test_paths.py +160 -0
- qaas_python-0.1.0/tests/test_prompt_overrides.py +388 -0
- qaas_python-0.1.0/tests/test_registry.py +187 -0
- qaas_python-0.1.0/tests/test_scorecard.py +403 -0
- qaas_python-0.1.0/tests/test_skills_actually_load.py +78 -0
- qaas_python-0.1.0/tests/test_store.py +122 -0
- qaas_python-0.1.0/tests/test_target_profile_is_honoured.py +149 -0
- qaas_python-0.1.0/tests/test_trace.py +376 -0
- qaas_python-0.1.0/tests/test_user_mcp_servers.py +152 -0
- qaas_python-0.1.0/tutorial/01-code-structure.md +241 -0
- qaas_python-0.1.0/tutorial/02-how-agents-communicate.md +963 -0
- qaas_python-0.1.0/tutorial/03-skills-and-hooks.md +607 -0
- qaas_python-0.1.0/tutorial/04-mcp-servers.md +709 -0
- qaas_python-0.1.0/tutorial/05-prompt-configuration.md +619 -0
- qaas_python-0.1.0/tutorial/06-jira-integration.md +938 -0
- qaas_python-0.1.0/tutorial/07-github-actions.md +499 -0
- qaas_python-0.1.0/tutorial/08-packaging-and-publishing.md +669 -0
- qaas_python-0.1.0/tutorial/09-guardrails-and-safety.md +1078 -0
- qaas_python-0.1.0/tutorial/README.md +88 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.qaas/
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.pyc
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
node_modules/
|
|
7
|
+
.DS_Store
|
|
8
|
+
.env
|
|
9
|
+
|
|
10
|
+
# Run debris — agents write these while exploring; they are not source.
|
|
11
|
+
.playwright-mcp/
|
|
12
|
+
/*.png
|
|
13
|
+
|
|
14
|
+
# Agent worktrees — real git worktrees, never repo content.
|
|
15
|
+
.claude/worktrees/
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# How this system works
|
|
2
|
+
|
|
3
|
+
A guide to `qaas` for someone who has never opened it. Read this before
|
|
4
|
+
`CLAUDE.md` (which is terse notes for people already oriented) and before
|
|
5
|
+
`qa-agent-system-architecture.md` (which is the original design, not the code).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. What it is, in one paragraph
|
|
10
|
+
|
|
11
|
+
`qaas` points a team of AI agents at an application, finds real defects in it,
|
|
12
|
+
reproduces them with runnable failing tests, files tickets, fixes some of them,
|
|
13
|
+
reviews the fixes, and verifies the fix actually worked. It is not a chatbot with
|
|
14
|
+
tools bolted on. It is a Python state machine that invokes agents the way a build
|
|
15
|
+
system invokes compilers: on a schedule, within a budget, with hard limits on
|
|
16
|
+
what each one may touch.
|
|
17
|
+
|
|
18
|
+
The thing it is pointed at is called the **target**. A bundled deliberately-buggy
|
|
19
|
+
demo app (`target-app/`) ships with it, along with a list of every bug seeded
|
|
20
|
+
into it (`target-app/defects.yaml`), so you can measure whether the agents
|
|
21
|
+
actually find things rather than take their word for it.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Entry point
|
|
26
|
+
|
|
27
|
+
There is exactly one:
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
# pyproject.toml
|
|
31
|
+
[project.scripts]
|
|
32
|
+
qaas = "qaas.cli:app"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`qaas` on the command line runs `src/qaas/cli.py`. Everything starts there. The
|
|
36
|
+
commands you will actually use:
|
|
37
|
+
|
|
38
|
+
| command | what it does | costs money? |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `qaas validate` | checks config, prompts and allowlists are coherent | no |
|
|
41
|
+
| `qaas doctor --target corvid` | what this target makes possible | no |
|
|
42
|
+
| `qaas run --mode pr-check --dry-run` | renders each agent's exact options and prompt | no |
|
|
43
|
+
| `qaas run --mode pr-check` | **a real run** | yes |
|
|
44
|
+
| `qaas runs` / `qaas show <id>` | what happened in past runs | no |
|
|
45
|
+
| `qaas score` | recall/precision against the golden ledger | no |
|
|
46
|
+
| `qaas sweep` | run + score + fail below the quality gate — the cron entry | yes |
|
|
47
|
+
| `qaas tracker-check` | Jira auth/permissions preflight, creates nothing | no |
|
|
48
|
+
|
|
49
|
+
The tracker and vcs backends are `local` in the committed config and must stay
|
|
50
|
+
that way — the offline test suite builds real adapters, and a committed `jira`
|
|
51
|
+
breaks it. Switch per-shell instead:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
QAAS_TRACKER=jira qaas run --mode nightly # files to real Jira
|
|
55
|
+
QAAS_TRACKER=jira qaas tracker-check # preflight only
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Start with `qaas validate`, then `qaas run --dry-run`. Neither calls the API.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 3. The shape of the code
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
src/qaas/
|
|
66
|
+
├── cli.py 866 entry point; every command above
|
|
67
|
+
├── conductor.py 452 THE STATE MACHINE — phases, budget, concurrency, loops
|
|
68
|
+
├── runner.py 118 invokes ONE agent, records what it cost
|
|
69
|
+
├── registry.py 275 turns an agent's YAML into SDK options
|
|
70
|
+
├── guardrails.py 413 the permission matrix, enforced in code
|
|
71
|
+
├── envelope.py 263 DefectEnvelope — the only type agents pass each other
|
|
72
|
+
├── store.py 225 run ledger, artifacts, versioned system map
|
|
73
|
+
├── config.py 201 loads and validates config/
|
|
74
|
+
├── tasks.py 349 builds the per-run task string for each agent
|
|
75
|
+
├── target.py 249 target profiles — what makes it portable
|
|
76
|
+
├── scorecard.py 424 measures recall/precision against the golden ledger
|
|
77
|
+
├── discover.py 227 guesses a target profile for `qaas init`
|
|
78
|
+
├── sdk_compat.py 52 fails loudly when the SDK changes hook names
|
|
79
|
+
├── prompts/ one .md per agent + _shared.md appended to all
|
|
80
|
+
├── mcp/ seven in-process tool servers (see §6)
|
|
81
|
+
└── adapters/ tracker (local | jira), vcs (local | github)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If you read three files, read `conductor.py`, `envelope.py`, `guardrails.py`.
|
|
85
|
+
Everything else is in service of those.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 4. The two decisions that explain everything else
|
|
90
|
+
|
|
91
|
+
**CONDUCTOR is Python, not a prompt.** The original design has an orchestrator
|
|
92
|
+
agent. It is implemented as an ordinary state machine instead, because *a model
|
|
93
|
+
cannot enforce a budget it is itself spending*. Phase ordering, concurrency,
|
|
94
|
+
retries, escalation and the budget governor are all plain code in
|
|
95
|
+
`conductor.py`. This also makes runs reproducible and cheap to unit-test — 547
|
|
96
|
+
tests run offline with no API calls.
|
|
97
|
+
|
|
98
|
+
**Each agent is its own top-level `query()`.** Not SDK subagents of a shared
|
|
99
|
+
parent. That is what gives each agent a real context boundary, an enforceable
|
|
100
|
+
per-agent tool allowlist, and its own `total_cost_usd`. Nesting them would pool
|
|
101
|
+
cost into one number and blur the allowlist that the whole permission model
|
|
102
|
+
depends on.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 5. The flow
|
|
107
|
+
|
|
108
|
+
### 5.1 One run, end to end
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
$ qaas run --mode full-loop
|
|
112
|
+
│
|
|
113
|
+
▼
|
|
114
|
+
cli.py ── loads config/system.yaml + config/agents/*.yaml + the target profile
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
conductor.py ── runs five phases in order, in-process
|
|
118
|
+
│
|
|
119
|
+
├── PHASE 1 map CARTOGRAPHER → system-map.json (versioned, pinned)
|
|
120
|
+
├── PHASE 2 discover CONDUIT, SURFACE → DefectEnvelopes [concurrent]
|
|
121
|
+
├── PHASE 3 reproduce FORGE → failing test per finding
|
|
122
|
+
├── PHASE 4 file CLERK → tickets
|
|
123
|
+
└── PHASE 5 verify PROOF ⇄ MENDER ⇄ ARBITER [bounded loop]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Each phase is a method on `Conductor`: `_phase_map`, `_phase_discover`,
|
|
127
|
+
`_phase_reproduce`, `_phase_file`, `_phase_verify`.
|
|
128
|
+
|
|
129
|
+
Which agents run is **config, not code** — `run_modes` in `config/system.yaml`:
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
pr-check: [CARTOGRAPHER, CONDUIT, SURFACE, FORGE, CLERK] $16
|
|
133
|
+
nightly: [CARTOGRAPHER, CONDUIT, SURFACE, FORGE, CLERK] $40
|
|
134
|
+
fix-cycle: [PROOF, MENDER, ARBITER] $20
|
|
135
|
+
full-loop: all eight $60
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Note the shape: **FORGE runs once per finding**, in a fresh context each time. So
|
|
139
|
+
cost scales with how much was found, not with how many agents exist.
|
|
140
|
+
|
|
141
|
+
### 5.2 Dispatching one agent
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
conductor._dispatch(spec, task)
|
|
145
|
+
│
|
|
146
|
+
├── Budget.check() raises BudgetExceeded → a control working, not an error
|
|
147
|
+
│
|
|
148
|
+
▼
|
|
149
|
+
runner.run_agent(spec, ctx, task)
|
|
150
|
+
│
|
|
151
|
+
├── registry.build_options(spec, ctx)
|
|
152
|
+
│ ├── system prompt ← prompts/<AGENT>.md + prompts/_shared.md
|
|
153
|
+
│ ├── task string ← tasks.py (which app, which env, which finding)
|
|
154
|
+
│ ├── skills ← .claude/skills/*/SKILL.md
|
|
155
|
+
│ ├── mcp_servers ← in-process, closed over ToolContext
|
|
156
|
+
│ ├── allowed_tools ← this agent's allowlist only
|
|
157
|
+
│ └── hooks ← PreToolUse / PostToolUse / Stop
|
|
158
|
+
│
|
|
159
|
+
▼
|
|
160
|
+
claude_agent_sdk.query(...) one top-level query, its own context
|
|
161
|
+
│
|
|
162
|
+
▼
|
|
163
|
+
AgentResult cost_usd, num_turns, subtype, error → written to the run ledger
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Failures are **captured, not raised**. One agent falling over costs the run that
|
|
167
|
+
agent's findings, not the whole run; the conductor decides whether to retry, skip
|
|
168
|
+
or escalate.
|
|
169
|
+
|
|
170
|
+
### 5.3 The remediation loop (phase 5)
|
|
171
|
+
|
|
172
|
+
This is the only phase with a cycle in it, so it is the only one that needs
|
|
173
|
+
explicit bounds:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
┌─────────────────────────────────────────────┐
|
|
177
|
+
▼ │
|
|
178
|
+
PROOF ──VERIFIED──▶ done │
|
|
179
|
+
│ │
|
|
180
|
+
├──REGRESSED──▶ escalate to a human │
|
|
181
|
+
│ │
|
|
182
|
+
NOT_FIXED │
|
|
183
|
+
│ │
|
|
184
|
+
├─ reopens ≥ max_proof_reopens ─▶ escalate │
|
|
185
|
+
▼ │
|
|
186
|
+
MENDER ──▶ ARBITER ──APPROVE────────────────────┘
|
|
187
|
+
│
|
|
188
|
+
├─ REQUEST_CHANGES ─▶ back to MENDER (capped at 2 trips)
|
|
189
|
+
└─ ESCALATE_TO_HUMAN ─▶ stop
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The bound is the point. Without `max_proof_reopens`, a fix that keeps missing the
|
|
193
|
+
defect cycles until the budget is gone and the run ends with no verdict and no
|
|
194
|
+
money left to reach one. Escalating after one reopen costs a human five minutes;
|
|
195
|
+
not escalating costs the whole run.
|
|
196
|
+
|
|
197
|
+
PROOF's verdict is a **typed ledger entry** written through a tool
|
|
198
|
+
(`record_verdict`), never parsed out of the agent's prose.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 6. Data: what actually moves between agents
|
|
203
|
+
|
|
204
|
+
### 6.1 The DefectEnvelope is the only inter-agent type
|
|
205
|
+
|
|
206
|
+
Agents never pass prose to each other. `envelope.py` defines a Pydantic model
|
|
207
|
+
with `extra="forbid"`, and an agent's only write path is the
|
|
208
|
+
`envelope.emit_envelope` tool, which validates and rejects with field-level
|
|
209
|
+
errors.
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
envelope_version id run_id discovered_by discovered_at
|
|
213
|
+
domain defect_class title summary
|
|
214
|
+
location evidence reproduction
|
|
215
|
+
impact severity confidence
|
|
216
|
+
suggested_owner suggested_fix_area autonomy_eligible
|
|
217
|
+
dedupe jira
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Two gates live on the model itself rather than in a prompt, because a prompt is a
|
|
221
|
+
request and a method is a rule:
|
|
222
|
+
|
|
223
|
+
- `has_evidence()` — an artifact or a failing test, or it is not a finding
|
|
224
|
+
- `is_fileable()` — evidence **and** confidence ≥ threshold **and** not
|
|
225
|
+
`not_reproducible`
|
|
226
|
+
|
|
227
|
+
`fingerprint()` deliberately **excludes** prose, line numbers, commit sha and
|
|
228
|
+
timestamps, so the same defect found twice by two differently-worded agents
|
|
229
|
+
hashes the same. That is what makes deduplication possible at all.
|
|
230
|
+
|
|
231
|
+
### 6.2 Where state lives
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
.qaas/ (gitignored — runtime state)
|
|
235
|
+
├── runs/<run-id>/
|
|
236
|
+
│ ├── ledger.jsonl append-only: every tool call, denial, escalation
|
|
237
|
+
│ ├── envelopes/*.json the findings
|
|
238
|
+
│ ├── artifacts/ screenshots, logs, traces
|
|
239
|
+
│ └── results/*.json per-agent cost, turns, duration
|
|
240
|
+
├── system-map/ versioned, shared across runs, pinned per run
|
|
241
|
+
├── memory.db SQLite: defect fingerprints for dedupe
|
|
242
|
+
└── tickets/*.json the local tracker (when tracker: local)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The ledger is the audit trail. `qaas show <run-id>` reads it. Evidence is
|
|
246
|
+
referenced by `artifact://<run>/<name>` URIs, and `resolve_artifact` rejects any
|
|
247
|
+
path escaping the store.
|
|
248
|
+
|
|
249
|
+
The system map is **versioned and pinned per run** so a bad map cannot
|
|
250
|
+
half-propagate through a run that already started.
|
|
251
|
+
|
|
252
|
+
### 6.3 The seven MCP servers
|
|
253
|
+
|
|
254
|
+
All in-process (`create_sdk_mcp_server`), no subprocesses, no protocol
|
|
255
|
+
implementation. They close over a `ToolContext` holding the run store, config,
|
|
256
|
+
agent spec and pinned map version — so validation, guardrails and persistence
|
|
257
|
+
happen where the state already is.
|
|
258
|
+
|
|
259
|
+
| server | what it gives an agent |
|
|
260
|
+
|---|---|
|
|
261
|
+
| `envelope` | `emit_envelope`, `get_system_map`, `put_artifact` |
|
|
262
|
+
| `test_runner` | `run_suite`, `run_single`, `run_n_times`, `affected_tests` |
|
|
263
|
+
| `env_control` | `spin_up`, `seed`, `reset`, `set_flag`, `impersonate`, `tear_down` |
|
|
264
|
+
| `defect_memory` | `search_similar`, `fingerprint`, `record`, `get_occurrences` |
|
|
265
|
+
| `contract_diff` | `diff_openapi`, `classify_breaking`, `generate_contract_test` |
|
|
266
|
+
| `tracker` | `create_issue`, `transition`, `link`, `search` |
|
|
267
|
+
| `vcs` | `create_branch`, `write_file`, `commit`, `push`, `open_pull_request` |
|
|
268
|
+
|
|
269
|
+
Tool results use `ok()` / `err()` from `mcp/context.py`. Errors are **returned,
|
|
270
|
+
not raised**, so an agent reads the reason and corrects itself instead of dying.
|
|
271
|
+
|
|
272
|
+
Playwright is the one exception — a real stdio subprocess, declared in
|
|
273
|
+
`registry.STDIO_SERVERS`.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 7. Agents are data, not code
|
|
278
|
+
|
|
279
|
+
An agent is **a prompt plus a YAML file**. Nothing else.
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
src/qaas/prompts/CONDUIT.md role, standards, what good looks like
|
|
283
|
+
config/agents/conduit.yaml model, budget, tools, skills, policy
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Adding an agent should require **no change** to `conductor.py`, `runner.py`,
|
|
287
|
+
`registry.py` or `guardrails.py`. A change to those files while adding an agent
|
|
288
|
+
is a sign something is wrong.
|
|
289
|
+
|
|
290
|
+
Where each kind of instruction belongs — this split matters and is easy to get
|
|
291
|
+
wrong:
|
|
292
|
+
|
|
293
|
+
| what | where | why |
|
|
294
|
+
|---|---|---|
|
|
295
|
+
| role, standards | `prompts/<AGENT>.md` | system prompt, stable across runs |
|
|
296
|
+
| house rules for all agents | `prompts/_shared.md` | appended to every prompt; not copy-pasted six times |
|
|
297
|
+
| **procedure** | `.claude/skills/<name>/SKILL.md` | loaded on demand, shared between agents |
|
|
298
|
+
| the per-run **task** | `tasks.py` | which app, which environment, which finding |
|
|
299
|
+
| **enforcement** | `guardrails.py` | a prompt is a request; code is a rule |
|
|
300
|
+
|
|
301
|
+
**Nothing in `tasks.py` or a prompt may name a specific application.** A prompt
|
|
302
|
+
that mentions one repo's layout or one app's seeded users works exactly once.
|
|
303
|
+
|
|
304
|
+
### The eight agents
|
|
305
|
+
|
|
306
|
+
| agent | layer | does |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| CARTOGRAPHER | map | services, routes, schema, ownership → `system-map.json` |
|
|
309
|
+
| CONDUIT | discovery | API contract drift; ships a failing contract test |
|
|
310
|
+
| SURFACE | discovery | drives the UI through real journeys |
|
|
311
|
+
| FORGE | triage | reproduces, minimises, measures flake, commits a failing test |
|
|
312
|
+
| CLERK | triage | dedupes, scores severity, routes, files — the only tracker writer |
|
|
313
|
+
| MENDER | remediation | the minimal fix, on a `fix/*` branch |
|
|
314
|
+
| ARBITER | remediation | adversarial review: APPROVE / REQUEST_CHANGES / ESCALATE |
|
|
315
|
+
| PROOF | verify | re-runs the original test → VERIFIED / NOT_FIXED / REGRESSED |
|
|
316
|
+
|
|
317
|
+
(The design doc names 16. Eight are built; CONDUCTOR is the Python state machine
|
|
318
|
+
rather than an agent; seven Phase-2 agents are not built.)
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 8. Guardrails: how agents are actually constrained
|
|
323
|
+
|
|
324
|
+
The critical fact, learned the hard way in this repo:
|
|
325
|
+
|
|
326
|
+
> An `allowed_tools` entry naming a whole tool **auto-approves it before
|
|
327
|
+
> `can_use_tool` is consulted.** A policy implemented only in that callback is
|
|
328
|
+
> silently dead code.
|
|
329
|
+
|
|
330
|
+
So primary enforcement is the **`PreToolUse` hook**, with `can_use_tool` as a
|
|
331
|
+
second layer for calls the allowlist did not auto-approve. Both call one
|
|
332
|
+
`check()` function so they cannot disagree.
|
|
333
|
+
|
|
334
|
+
What is enforced:
|
|
335
|
+
|
|
336
|
+
- **Path scoping** — writes resolved and checked against `write_paths`. FORGE and
|
|
337
|
+
MENDER only; everyone else denied.
|
|
338
|
+
- **Branch scoping** — git writes matched against branch patterns
|
|
339
|
+
(`qa/repro/*`, `fix/*`). `main` and force-push denied outright.
|
|
340
|
+
- **Ticket rate limit** — `max_tickets_per_run: 10`; over cap the call is denied
|
|
341
|
+
and the conductor escalates instead of filing.
|
|
342
|
+
- **Forbidden path classes** — migrations, auth, payment, secrets, infra, CI stop
|
|
343
|
+
at a human however small the change looks.
|
|
344
|
+
- **Diff budget** — counted per distinct file.
|
|
345
|
+
- **Immutable test** — MENDER may not edit the test recorded on the envelope.
|
|
346
|
+
|
|
347
|
+
Denials **return a reason and are logged**; they never kill the turn. The agent
|
|
348
|
+
reads the denial and adapts.
|
|
349
|
+
|
|
350
|
+
`ALWAYS_GRANTED` (ToolSearch, Skill, TodoWrite, Task, Agent) is read by both
|
|
351
|
+
`build_allowed_tools` and the guardrail — a mismatch there silently disables every
|
|
352
|
+
skill. Denying `ToolSearch` breaks MCP access entirely, since MCP tools arrive
|
|
353
|
+
deferred.
|
|
354
|
+
|
|
355
|
+
**Merge is impossible by construction.** No merge method exists anywhere in the
|
|
356
|
+
codebase, `gh pr merge` is refused by `FORBIDDEN_BASH`, and pull requests open as
|
|
357
|
+
drafts. Merging is a human decision.
|
|
358
|
+
|
|
359
|
+
### Hooks enforce the output contract
|
|
360
|
+
|
|
361
|
+
- **`Stop`** blocks an agent that has not called its `must_call` tools, *while it
|
|
362
|
+
still has a turn to fix it* — the conductor would only find out afterwards. It
|
|
363
|
+
honours `stop_hook_active`; blocking twice burns budget.
|
|
364
|
+
- **`PostToolUse`** tells an agent immediately when an emitted envelope was
|
|
365
|
+
**held** rather than filed, since discovering that at the end is too late to
|
|
366
|
+
attach evidence.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 9. Targets: why this is portable
|
|
371
|
+
|
|
372
|
+
`target.py` + `config/targets/*.yaml`. The load-bearing field is
|
|
373
|
+
`environment.mode`:
|
|
374
|
+
|
|
375
|
+
| mode | meaning |
|
|
376
|
+
|---|---|
|
|
377
|
+
| `none` | static reads only — no running app |
|
|
378
|
+
| `external` | exercise it, but never reset it |
|
|
379
|
+
| `compose` | own the lifecycle: spin up, seed, reset, tear down |
|
|
380
|
+
|
|
381
|
+
`profile.capabilities()` decides which agents can usefully run; `qaas doctor`
|
|
382
|
+
reports it. **Credentials are never in a profile** — it names environment
|
|
383
|
+
variables, and the values live outside the repo.
|
|
384
|
+
|
|
385
|
+
Pointing this at a different application is a new YAML file, not a code change.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 10. Calibration: the part that keeps it honest
|
|
390
|
+
|
|
391
|
+
`target-app/` is deliberately buggy. `target-app/defects.yaml` is the **golden
|
|
392
|
+
ledger**: every seeded defect with the domain, severity and location a correct
|
|
393
|
+
agent should conclude. `scorecard.py` measures recall, precision, false-positive
|
|
394
|
+
rate and severity agreement against it.
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
$ qaas score
|
|
398
|
+
recall 81% (13 of 16)
|
|
399
|
+
precision 100%
|
|
400
|
+
false positives 0
|
|
401
|
+
severity agreement 100%
|
|
402
|
+
cost per accepted $1.12
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The `not_defects` section plants correct-but-suspicious code, so **precision is
|
|
406
|
+
measured rather than assumed**.
|
|
407
|
+
|
|
408
|
+
Two rules:
|
|
409
|
+
|
|
410
|
+
1. **Run `qaas score` after changing any prompt, threshold or model.** It is the
|
|
411
|
+
only way to know whether a change helped.
|
|
412
|
+
2. **The ledger is not writable by the agents it scores** — deliberately. An
|
|
413
|
+
agent that can retire an entry can raise its own recall without fixing
|
|
414
|
+
anything. Retiring a repaired defect (`fixed_in: <ref>`) is a human's job at
|
|
415
|
+
merge time.
|
|
416
|
+
|
|
417
|
+
And the caveat that matters most: seeded defects are easier than real ones. Treat
|
|
418
|
+
100% recall on `defects.yaml` as a floor, never as evidence the system is ready
|
|
419
|
+
for a real codebase.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 11. Testing
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
pytest # 547 tests, no API calls, no network, free
|
|
427
|
+
pytest -m docker # 18 tests, needs target-app running
|
|
428
|
+
pytest -m 'llm' # real API calls — excluded by default
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
The markers `llm`, `docker`, `github`, `jira` are deselected by `addopts` in
|
|
432
|
+
`pyproject.toml`. **The default `pytest` run is offline and free, and must stay
|
|
433
|
+
that way.**
|
|
434
|
+
|
|
435
|
+
Four tiers, cheapest first:
|
|
436
|
+
|
|
437
|
+
1. `pytest` — envelope, guardrails, MCP tools, adapters, scoring
|
|
438
|
+
2. `qaas run --dry-run` — asserts assembled options match each allowlist
|
|
439
|
+
3. `pytest -m llm` — one cheap live run per agent, asserting shape not content
|
|
440
|
+
4. `qaas run && qaas score` — the honest number
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 12. Reading order for a newcomer
|
|
445
|
+
|
|
446
|
+
1. `qaas validate` then `qaas run --mode pr-check --dry-run` — see the machine
|
|
447
|
+
describe itself, for free
|
|
448
|
+
2. `envelope.py` — the contract everything else moves
|
|
449
|
+
3. `conductor.py::run` — the five phases
|
|
450
|
+
4. `config/agents/conduit.yaml` + `prompts/CONDUIT.md` — what an agent *is*
|
|
451
|
+
5. `guardrails.py::check` — the one function both enforcement points call
|
|
452
|
+
6. `.qaas/runs/<id>/ledger.jsonl` from a real run — what actually happened
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## 13. Known gaps
|
|
457
|
+
|
|
458
|
+
Honesty about what is *not* proven, so nobody inherits a false impression:
|
|
459
|
+
|
|
460
|
+
- **Eight of sixteen agents.** KEYSTONE, VAULT, PULSE, USHER, WARDEN, GAUGE and
|
|
461
|
+
CHRONICLE are designed but not built.
|
|
462
|
+
- **The extensibility claim is untested.** "A new agent needs only a prompt and a
|
|
463
|
+
YAML" is the architecture's central promise, and no one has added a ninth agent
|
|
464
|
+
to check it.
|
|
465
|
+
- **`fix-cycle` leaves the working tree on a `fix/*` branch.** Harmless when
|
|
466
|
+
watched; it would corrupt the next run of an unattended `qaas sweep` on cron.
|
|
467
|
+
- **Only ever run against the bundled demo app**, whose bugs it was told about.
|
|
468
|
+
- **`wont_fix` and `duplicate`** have no matching status in the connected Jira
|
|
469
|
+
workflow, so those transitions would fail.
|