ai-testing-swarm 0.1.1__tar.gz → 0.1.11__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.
Files changed (38) hide show
  1. ai_testing_swarm-0.1.11/PKG-INFO +231 -0
  2. ai_testing_swarm-0.1.11/README.md +222 -0
  3. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/pyproject.toml +1 -1
  4. ai_testing_swarm-0.1.11/src/ai_testing_swarm/__init__.py +1 -0
  5. ai_testing_swarm-0.1.11/src/ai_testing_swarm/agents/execution_agent.py +186 -0
  6. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/agents/learning_agent.py +6 -4
  7. ai_testing_swarm-0.1.11/src/ai_testing_swarm/agents/test_planner_agent.py +396 -0
  8. ai_testing_swarm-0.1.11/src/ai_testing_swarm/cli.py +136 -0
  9. ai_testing_swarm-0.1.11/src/ai_testing_swarm/core/config.py +22 -0
  10. ai_testing_swarm-0.1.11/src/ai_testing_swarm/core/openai_client.py +182 -0
  11. ai_testing_swarm-0.1.11/src/ai_testing_swarm/core/openapi_loader.py +130 -0
  12. ai_testing_swarm-0.1.11/src/ai_testing_swarm/core/safety.py +32 -0
  13. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/orchestrator.py +50 -28
  14. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/reporting/report_writer.py +61 -3
  15. ai_testing_swarm-0.1.11/src/ai_testing_swarm.egg-info/PKG-INFO +231 -0
  16. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm.egg-info/SOURCES.txt +5 -0
  17. ai_testing_swarm-0.1.11/tests/test_openapi_loader.py +18 -0
  18. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/tests/test_swarm_api.py +7 -1
  19. ai_testing_swarm-0.1.1/PKG-INFO +0 -55
  20. ai_testing_swarm-0.1.1/README.md +0 -46
  21. ai_testing_swarm-0.1.1/src/ai_testing_swarm/__init__.py +0 -1
  22. ai_testing_swarm-0.1.1/src/ai_testing_swarm/agents/execution_agent.py +0 -64
  23. ai_testing_swarm-0.1.1/src/ai_testing_swarm/agents/test_planner_agent.py +0 -111
  24. ai_testing_swarm-0.1.1/src/ai_testing_swarm/cli.py +0 -78
  25. ai_testing_swarm-0.1.1/src/ai_testing_swarm.egg-info/PKG-INFO +0 -55
  26. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/setup.cfg +0 -0
  27. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/agents/__init__.py +0 -0
  28. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/agents/llm_reasoning_agent.py +0 -0
  29. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/agents/release_gate_agent.py +0 -0
  30. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/agents/test_writer_agent.py +0 -0
  31. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/agents/ui_agent.py +0 -0
  32. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/core/__init__.py +0 -0
  33. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/core/api_client.py +0 -0
  34. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/core/curl_parser.py +0 -0
  35. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm/reporting/__init__.py +0 -0
  36. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm.egg-info/dependency_links.txt +0 -0
  37. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm.egg-info/entry_points.txt +0 -0
  38. {ai_testing_swarm-0.1.1 → ai_testing_swarm-0.1.11}/src/ai_testing_swarm.egg-info/top_level.txt +0 -0
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-testing-swarm
3
+ Version: 0.1.11
4
+ Summary: AI-powered testing swarm
5
+ Author-email: Arif Shah <ashah7775@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+
10
+ # AI Testing Swarm
11
+
12
+ AI Testing Swarm is a **super-advanced, mutation-driven API testing framework** (with optional OpenAPI + OpenAI augmentation) built on top of **pytest**.
13
+
14
+ It generates a large set of deterministic negative/edge/security test cases for an API request, executes them (optionally in parallel, with retries/throttling), and produces a JSON report with summaries.
15
+
16
+ > Notes:
17
+ > - UI testing is not the focus of the current releases.
18
+ > - OpenAI features are **optional** and disabled by default.
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install ai-testing-swarm
26
+ ```
27
+
28
+ CLI entrypoint:
29
+
30
+ ```bash
31
+ ai-test --help
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Quick start (cURL input)
37
+
38
+ Create `request.json`:
39
+
40
+ ```json
41
+ {
42
+ "curl": "curl --location https://postman-echo.com/post --header \"Content-Type: application/json\" --data \"{\\\"hello\\\":\\\"world\\\",\\\"count\\\":1}\""
43
+ }
44
+ ```
45
+
46
+ Run:
47
+
48
+ ```bash
49
+ ai-test --input request.json
50
+ ```
51
+
52
+ A JSON report is written under:
53
+
54
+ - `./ai_swarm_reports/<METHOD>_<endpoint>/<METHOD>_<endpoint>_<timestamp>.json`
55
+
56
+ Reports include:
57
+ - per-test results
58
+ - summary counts by status code / failure type
59
+ - optional AI summary (if enabled)
60
+
61
+ ---
62
+
63
+ ## Input formats
64
+
65
+ ### 1) Raw cURL
66
+
67
+ ```json
68
+ { "curl": "curl ..." }
69
+ ```
70
+
71
+ ### 2) Normalized request
72
+
73
+ ```json
74
+ {
75
+ "method": "POST",
76
+ "url": "https://example.com/api/login",
77
+ "headers": {"content-type": "application/json"},
78
+ "params": {"a": "b"},
79
+ "body": {"username": "u", "password": "p"}
80
+ }
81
+ ```
82
+
83
+ ### 3) OpenAPI-driven (optional)
84
+
85
+ ```json
86
+ {
87
+ "openapi": "./openapi.json",
88
+ "path": "/pets",
89
+ "method": "get",
90
+ "headers": {"accept": "application/json"},
91
+ "path_params": {"petId": "123"},
92
+ "query_params": {"limit": 10},
93
+ "body": null
94
+ }
95
+ ```
96
+
97
+ - OpenAPI **JSON** works by default.
98
+ - OpenAPI **YAML** requires `PyYAML` installed.
99
+ - Base URL is read from `spec.servers[0].url`.
100
+ - Override with `AI_SWARM_OPENAPI_BASE_URL` if your spec doesn’t include servers.
101
+
102
+ ---
103
+
104
+ ## What test cases are generated?
105
+
106
+ The swarm always includes:
107
+ - **happy_path** (baseline)
108
+
109
+ Then generates broad coverage across:
110
+ - **Method misuse**: same path with wrong HTTP methods (GET/PUT/PATCH/DELETE etc.)
111
+ - **Headers**: missing/invalid `Content-Type`, accept variations, and other header tampering
112
+ - **Auth** (if `Authorization` header exists): missing/invalid token tests
113
+ - **Body/query mutations** (per field):
114
+ - missing / null / empty / whitespace
115
+ - type probes (int/bool/float/array/object)
116
+ - boundary inputs (very long strings, huge ints, negative values)
117
+ - unicode + special character payloads
118
+ - **Security payload probes** (per field): SQLi/XSS/path traversal/log4j patterns
119
+ - **Whole-body mutations**: null body, empty object, extra unexpected field
120
+
121
+ > Output is deterministic unless OpenAI augmentation is enabled.
122
+
123
+ ---
124
+
125
+ ## Safety mode (recommended for CI/demos)
126
+
127
+ Mutation testing can be noisy and may accidentally stress a real environment.
128
+ To force safe demo runs only against public test hosts:
129
+
130
+ ```bash
131
+ ai-test --input request.json --public-only
132
+ ```
133
+
134
+ Or via env:
135
+
136
+ ```bash
137
+ export AI_SWARM_PUBLIC_ONLY=1
138
+ ```
139
+
140
+ Allowed hosts in public-only mode:
141
+ - `httpbin.org`
142
+ - `postman-echo.com`
143
+ - `reqres.in`
144
+
145
+ ---
146
+
147
+ ## Performance features
148
+
149
+ ### Parallel execution
150
+ - Enabled by default via thread pool.
151
+ - Control with:
152
+ - `AI_SWARM_WORKERS` (default: `5`)
153
+
154
+ ### Retry + backoff (flaky endpoints)
155
+ - Retries on transient errors and status codes (408/429/5xx etc.)
156
+ - Control with:
157
+ - `AI_SWARM_RETRY_COUNT` (default: `1`)
158
+ - `AI_SWARM_RETRY_BACKOFF_MS` (default: `250`)
159
+
160
+ ### Throttling (RPS)
161
+ - Global throttle to avoid hammering a target:
162
+ - `AI_SWARM_RPS` (default: `0` = disabled)
163
+
164
+ ### Max test cap
165
+ - Avoids accidental DoS / CI timeouts:
166
+ - `AI_SWARM_MAX_TESTS` (default: `80`)
167
+
168
+ ---
169
+
170
+ ## Reporting
171
+
172
+ Reports include:
173
+ - `summary.counts_by_failure_type`
174
+ - `summary.counts_by_status_code`
175
+ - `summary.slow_tests` (based on SLA)
176
+
177
+ SLA threshold:
178
+ - `AI_SWARM_SLA_MS` (default: `2000`)
179
+
180
+ Security:
181
+ - Sensitive headers are redacted in the report (Authorization/Cookie/api tokens etc.)
182
+
183
+ ---
184
+
185
+ ## Optional OpenAI augmentation (advanced)
186
+
187
+ ### A) Generate additional test cases (planner augmentation)
188
+ Enable:
189
+
190
+ ```bash
191
+ export AI_SWARM_USE_OPENAI=1
192
+ export OPENAI_API_KEY=...
193
+ export AI_SWARM_MAX_AI_TESTS=30
194
+ ```
195
+
196
+ ### B) Human-readable AI summary in report
197
+ Enable:
198
+
199
+ ```bash
200
+ export AI_SWARM_USE_OPENAI=1
201
+ export AI_SWARM_AI_SUMMARY=1
202
+ export OPENAI_API_KEY=...
203
+ ```
204
+
205
+ Model selection:
206
+ - `AI_SWARM_OPENAI_MODEL` (default: `gpt-4.1-mini`)
207
+
208
+ ---
209
+
210
+ ## CLI help
211
+
212
+ ```bash
213
+ ai-test --help
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Release decisions
219
+
220
+ The swarm produces a release decision:
221
+ - `APPROVE_RELEASE`
222
+ - `APPROVE_RELEASE_WITH_RISKS`
223
+ - `REJECT_RELEASE`
224
+
225
+ The decision is derived from deterministic rules (not an LLM).
226
+
227
+ ---
228
+
229
+ ## License
230
+
231
+ MIT
@@ -0,0 +1,222 @@
1
+ # AI Testing Swarm
2
+
3
+ AI Testing Swarm is a **super-advanced, mutation-driven API testing framework** (with optional OpenAPI + OpenAI augmentation) built on top of **pytest**.
4
+
5
+ It generates a large set of deterministic negative/edge/security test cases for an API request, executes them (optionally in parallel, with retries/throttling), and produces a JSON report with summaries.
6
+
7
+ > Notes:
8
+ > - UI testing is not the focus of the current releases.
9
+ > - OpenAI features are **optional** and disabled by default.
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install ai-testing-swarm
17
+ ```
18
+
19
+ CLI entrypoint:
20
+
21
+ ```bash
22
+ ai-test --help
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Quick start (cURL input)
28
+
29
+ Create `request.json`:
30
+
31
+ ```json
32
+ {
33
+ "curl": "curl --location https://postman-echo.com/post --header \"Content-Type: application/json\" --data \"{\\\"hello\\\":\\\"world\\\",\\\"count\\\":1}\""
34
+ }
35
+ ```
36
+
37
+ Run:
38
+
39
+ ```bash
40
+ ai-test --input request.json
41
+ ```
42
+
43
+ A JSON report is written under:
44
+
45
+ - `./ai_swarm_reports/<METHOD>_<endpoint>/<METHOD>_<endpoint>_<timestamp>.json`
46
+
47
+ Reports include:
48
+ - per-test results
49
+ - summary counts by status code / failure type
50
+ - optional AI summary (if enabled)
51
+
52
+ ---
53
+
54
+ ## Input formats
55
+
56
+ ### 1) Raw cURL
57
+
58
+ ```json
59
+ { "curl": "curl ..." }
60
+ ```
61
+
62
+ ### 2) Normalized request
63
+
64
+ ```json
65
+ {
66
+ "method": "POST",
67
+ "url": "https://example.com/api/login",
68
+ "headers": {"content-type": "application/json"},
69
+ "params": {"a": "b"},
70
+ "body": {"username": "u", "password": "p"}
71
+ }
72
+ ```
73
+
74
+ ### 3) OpenAPI-driven (optional)
75
+
76
+ ```json
77
+ {
78
+ "openapi": "./openapi.json",
79
+ "path": "/pets",
80
+ "method": "get",
81
+ "headers": {"accept": "application/json"},
82
+ "path_params": {"petId": "123"},
83
+ "query_params": {"limit": 10},
84
+ "body": null
85
+ }
86
+ ```
87
+
88
+ - OpenAPI **JSON** works by default.
89
+ - OpenAPI **YAML** requires `PyYAML` installed.
90
+ - Base URL is read from `spec.servers[0].url`.
91
+ - Override with `AI_SWARM_OPENAPI_BASE_URL` if your spec doesn’t include servers.
92
+
93
+ ---
94
+
95
+ ## What test cases are generated?
96
+
97
+ The swarm always includes:
98
+ - **happy_path** (baseline)
99
+
100
+ Then generates broad coverage across:
101
+ - **Method misuse**: same path with wrong HTTP methods (GET/PUT/PATCH/DELETE etc.)
102
+ - **Headers**: missing/invalid `Content-Type`, accept variations, and other header tampering
103
+ - **Auth** (if `Authorization` header exists): missing/invalid token tests
104
+ - **Body/query mutations** (per field):
105
+ - missing / null / empty / whitespace
106
+ - type probes (int/bool/float/array/object)
107
+ - boundary inputs (very long strings, huge ints, negative values)
108
+ - unicode + special character payloads
109
+ - **Security payload probes** (per field): SQLi/XSS/path traversal/log4j patterns
110
+ - **Whole-body mutations**: null body, empty object, extra unexpected field
111
+
112
+ > Output is deterministic unless OpenAI augmentation is enabled.
113
+
114
+ ---
115
+
116
+ ## Safety mode (recommended for CI/demos)
117
+
118
+ Mutation testing can be noisy and may accidentally stress a real environment.
119
+ To force safe demo runs only against public test hosts:
120
+
121
+ ```bash
122
+ ai-test --input request.json --public-only
123
+ ```
124
+
125
+ Or via env:
126
+
127
+ ```bash
128
+ export AI_SWARM_PUBLIC_ONLY=1
129
+ ```
130
+
131
+ Allowed hosts in public-only mode:
132
+ - `httpbin.org`
133
+ - `postman-echo.com`
134
+ - `reqres.in`
135
+
136
+ ---
137
+
138
+ ## Performance features
139
+
140
+ ### Parallel execution
141
+ - Enabled by default via thread pool.
142
+ - Control with:
143
+ - `AI_SWARM_WORKERS` (default: `5`)
144
+
145
+ ### Retry + backoff (flaky endpoints)
146
+ - Retries on transient errors and status codes (408/429/5xx etc.)
147
+ - Control with:
148
+ - `AI_SWARM_RETRY_COUNT` (default: `1`)
149
+ - `AI_SWARM_RETRY_BACKOFF_MS` (default: `250`)
150
+
151
+ ### Throttling (RPS)
152
+ - Global throttle to avoid hammering a target:
153
+ - `AI_SWARM_RPS` (default: `0` = disabled)
154
+
155
+ ### Max test cap
156
+ - Avoids accidental DoS / CI timeouts:
157
+ - `AI_SWARM_MAX_TESTS` (default: `80`)
158
+
159
+ ---
160
+
161
+ ## Reporting
162
+
163
+ Reports include:
164
+ - `summary.counts_by_failure_type`
165
+ - `summary.counts_by_status_code`
166
+ - `summary.slow_tests` (based on SLA)
167
+
168
+ SLA threshold:
169
+ - `AI_SWARM_SLA_MS` (default: `2000`)
170
+
171
+ Security:
172
+ - Sensitive headers are redacted in the report (Authorization/Cookie/api tokens etc.)
173
+
174
+ ---
175
+
176
+ ## Optional OpenAI augmentation (advanced)
177
+
178
+ ### A) Generate additional test cases (planner augmentation)
179
+ Enable:
180
+
181
+ ```bash
182
+ export AI_SWARM_USE_OPENAI=1
183
+ export OPENAI_API_KEY=...
184
+ export AI_SWARM_MAX_AI_TESTS=30
185
+ ```
186
+
187
+ ### B) Human-readable AI summary in report
188
+ Enable:
189
+
190
+ ```bash
191
+ export AI_SWARM_USE_OPENAI=1
192
+ export AI_SWARM_AI_SUMMARY=1
193
+ export OPENAI_API_KEY=...
194
+ ```
195
+
196
+ Model selection:
197
+ - `AI_SWARM_OPENAI_MODEL` (default: `gpt-4.1-mini`)
198
+
199
+ ---
200
+
201
+ ## CLI help
202
+
203
+ ```bash
204
+ ai-test --help
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Release decisions
210
+
211
+ The swarm produces a release decision:
212
+ - `APPROVE_RELEASE`
213
+ - `APPROVE_RELEASE_WITH_RISKS`
214
+ - `REJECT_RELEASE`
215
+
216
+ The decision is derived from deterministic rules (not an LLM).
217
+
218
+ ---
219
+
220
+ ## License
221
+
222
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ai-testing-swarm"
7
- version = "0.1.1"
7
+ version = "0.1.11"
8
8
  description = "AI-powered testing swarm"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.11"
@@ -0,0 +1,186 @@
1
+ import random
2
+ import time
3
+ from copy import deepcopy
4
+
5
+ import requests
6
+
7
+ from ai_testing_swarm.core.config import (
8
+ AI_SWARM_RETRY_BACKOFF_MS,
9
+ AI_SWARM_RETRY_COUNT,
10
+ AI_SWARM_RPS,
11
+ )
12
+
13
+
14
+ class ExecutionAgent:
15
+ def _set_path(self, obj: dict, path: list[str], value, remove: bool = False):
16
+ """Set/remove nested key in a dict. Creates intermediate dicts if needed."""
17
+ if not path:
18
+ return
19
+ cur = obj
20
+ for key in path[:-1]:
21
+ if key not in cur or not isinstance(cur[key], dict):
22
+ cur[key] = {}
23
+ cur = cur[key]
24
+
25
+ last = path[-1]
26
+ if remove:
27
+ cur.pop(last, None)
28
+ else:
29
+ cur[last] = value
30
+
31
+ def apply_mutation(self, data: dict, mutation: dict):
32
+ if not mutation:
33
+ return deepcopy(data)
34
+
35
+ data = deepcopy(data)
36
+ path = mutation.get("path") or []
37
+ op = mutation.get("operation")
38
+
39
+ if op == "REMOVE":
40
+ self._set_path(data, path, None, remove=True)
41
+ elif op == "REPLACE":
42
+ self._set_path(data, path, mutation.get("new_value"), remove=False)
43
+
44
+ return data
45
+
46
+ def _throttle(self):
47
+ # Optional global throttling to avoid hammering target services.
48
+ if AI_SWARM_RPS and AI_SWARM_RPS > 0:
49
+ time.sleep(1.0 / float(AI_SWARM_RPS))
50
+
51
+ def _should_retry(self, status_code: int | None, error: str | None) -> bool:
52
+ if error:
53
+ return True
54
+ if status_code in (408, 425, 429, 500, 502, 503, 504):
55
+ return True
56
+ return False
57
+
58
+ def execute(self, request: dict, test: dict):
59
+ mutation = test.get("mutation")
60
+
61
+ # Method (allow method mutations)
62
+ method = request["method"].upper()
63
+ if mutation and mutation.get("target") == "method" and mutation.get("operation") == "REPLACE":
64
+ method = str(mutation.get("new_value") or method).upper()
65
+
66
+ params = (
67
+ self.apply_mutation(request.get("params", {}) or {}, mutation)
68
+ if mutation and mutation.get("target") == "query"
69
+ else deepcopy(request.get("params", {}) or {})
70
+ )
71
+
72
+ body = deepcopy(request.get("body", {}) or {})
73
+ if mutation and mutation.get("target") == "body":
74
+ body = self.apply_mutation(body, mutation)
75
+ elif mutation and mutation.get("target") == "body_whole" and mutation.get("operation") == "REPLACE":
76
+ body = mutation.get("new_value")
77
+
78
+ headers = deepcopy(request.get("headers", {}) or {})
79
+ if mutation and mutation.get("target") == "headers":
80
+ headers = self.apply_mutation(headers, mutation)
81
+
82
+ # Payload strategy (basic content-type aware)
83
+ send_json = method != "GET"
84
+ ct = (headers.get("content-type") or headers.get("Content-Type") or "").lower()
85
+
86
+ json_payload = None
87
+ data_payload = None
88
+ if method == "GET":
89
+ json_payload = None
90
+ data_payload = None
91
+ else:
92
+ # If body is not a dict, send raw data
93
+ if not isinstance(body, (dict, list)):
94
+ send_json = False
95
+ data_payload = body
96
+ elif "application/x-www-form-urlencoded" in ct:
97
+ send_json = False
98
+ data_payload = body
99
+ else:
100
+ json_payload = body
101
+
102
+ # Execute with light retry/backoff on transient failures.
103
+ attempt = 0
104
+ last_error = None
105
+ last_response = None
106
+
107
+ while True:
108
+ attempt += 1
109
+ self._throttle()
110
+
111
+ started = time.time()
112
+ try:
113
+ resp = requests.request(
114
+ method=method,
115
+ url=request["url"],
116
+ headers=headers,
117
+ params=params or None,
118
+ json=json_payload if send_json else None,
119
+ data=data_payload if not send_json else None,
120
+ timeout=15,
121
+ )
122
+ elapsed_ms = int((time.time() - started) * 1000)
123
+
124
+ last_error = None
125
+ last_response = (resp, elapsed_ms)
126
+
127
+ if attempt <= (AI_SWARM_RETRY_COUNT + 1) and self._should_retry(resp.status_code, None):
128
+ # jittered backoff
129
+ backoff = (AI_SWARM_RETRY_BACKOFF_MS / 1000.0) * (2 ** (attempt - 1))
130
+ backoff = backoff * (0.75 + random.random() * 0.5)
131
+ time.sleep(min(backoff, 5.0))
132
+ if attempt <= AI_SWARM_RETRY_COUNT:
133
+ continue
134
+
135
+ try:
136
+ body_snippet = resp.json()
137
+ except Exception:
138
+ body_snippet = resp.text
139
+
140
+ return {
141
+ "name": test["name"],
142
+ "mutation": mutation,
143
+ "request": {
144
+ "method": method,
145
+ "url": request["url"],
146
+ "headers": headers,
147
+ "params": params,
148
+ "body": body if method != "GET" else None,
149
+ },
150
+ "response": {
151
+ "status_code": resp.status_code,
152
+ "elapsed_ms": elapsed_ms,
153
+ "attempt": attempt,
154
+ "body_snippet": body_snippet,
155
+ },
156
+ }
157
+
158
+ except requests.RequestException as e:
159
+ elapsed_ms = int((time.time() - started) * 1000)
160
+ last_error = str(e)
161
+ last_response = None
162
+
163
+ if attempt <= AI_SWARM_RETRY_COUNT and self._should_retry(None, last_error):
164
+ backoff = (AI_SWARM_RETRY_BACKOFF_MS / 1000.0) * (2 ** (attempt - 1))
165
+ backoff = backoff * (0.75 + random.random() * 0.5)
166
+ time.sleep(min(backoff, 5.0))
167
+ continue
168
+
169
+ return {
170
+ "name": test["name"],
171
+ "mutation": mutation,
172
+ "request": {
173
+ "method": method,
174
+ "url": request["url"],
175
+ "headers": headers,
176
+ "params": params,
177
+ "body": body if method != "GET" else None,
178
+ },
179
+ "response": {
180
+ "status_code": None,
181
+ "elapsed_ms": elapsed_ms,
182
+ "attempt": attempt,
183
+ "error": last_error,
184
+ "body_snippet": None,
185
+ },
186
+ }
@@ -2,6 +2,7 @@ import json
2
2
  import os
3
3
 
4
4
  DB = "memory/failure_memory.json"
5
+ MAX_ENTRIES = 200
5
6
 
6
7
 
7
8
  class LearningAgent:
@@ -20,10 +21,11 @@ class LearningAgent:
20
21
  # Corrupted file → reset
21
22
  data = []
22
23
 
23
- data.append({
24
- "test": test_name,
25
- "reason": reasoning
26
- })
24
+ data.append({"test": test_name, "reason": reasoning})
25
+
26
+ # Keep file bounded
27
+ if len(data) > MAX_ENTRIES:
28
+ data = data[-MAX_ENTRIES:]
27
29
 
28
30
  with open(DB, "w") as f:
29
31
  json.dump(data, f, indent=2)