agctl 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.
Files changed (72) hide show
  1. agctl-0.1.0/.claude/agents/docs-watcher.md +94 -0
  2. agctl-0.1.0/.gitignore +22 -0
  3. agctl-0.1.0/CLAUDE.md +13 -0
  4. agctl-0.1.0/LICENSE +21 -0
  5. agctl-0.1.0/PKG-INFO +414 -0
  6. agctl-0.1.0/README.md +370 -0
  7. agctl-0.1.0/agctl/__init__.py +1 -0
  8. agctl-0.1.0/agctl/assertion_registry.py +243 -0
  9. agctl-0.1.0/agctl/assertions.py +135 -0
  10. agctl-0.1.0/agctl/cli.py +163 -0
  11. agctl-0.1.0/agctl/clients/__init__.py +0 -0
  12. agctl-0.1.0/agctl/clients/db_client.py +94 -0
  13. agctl-0.1.0/agctl/clients/db_driver_protocol.py +27 -0
  14. agctl-0.1.0/agctl/clients/db_drivers/__init__.py +0 -0
  15. agctl-0.1.0/agctl/clients/db_drivers/postgresql.py +92 -0
  16. agctl-0.1.0/agctl/clients/http_client.py +116 -0
  17. agctl-0.1.0/agctl/clients/kafka_client.py +398 -0
  18. agctl-0.1.0/agctl/command.py +74 -0
  19. agctl-0.1.0/agctl/commands/__init__.py +1 -0
  20. agctl-0.1.0/agctl/commands/check_commands.py +115 -0
  21. agctl-0.1.0/agctl/commands/config_commands.py +175 -0
  22. agctl-0.1.0/agctl/commands/db_commands.py +329 -0
  23. agctl-0.1.0/agctl/commands/discover_commands.py +372 -0
  24. agctl-0.1.0/agctl/commands/http_commands.py +534 -0
  25. agctl-0.1.0/agctl/commands/kafka_commands.py +485 -0
  26. agctl-0.1.0/agctl/config/__init__.py +5 -0
  27. agctl-0.1.0/agctl/config/loader.py +108 -0
  28. agctl-0.1.0/agctl/config/models.py +104 -0
  29. agctl-0.1.0/agctl/config/resolver.py +84 -0
  30. agctl-0.1.0/agctl/config/validator.py +86 -0
  31. agctl-0.1.0/agctl/errors.py +46 -0
  32. agctl-0.1.0/agctl/output.py +25 -0
  33. agctl-0.1.0/agctl/params.py +29 -0
  34. agctl-0.1.0/agctl/plugin_protocol.py +42 -0
  35. agctl-0.1.0/agctl/resolution.py +77 -0
  36. agctl-0.1.0/docs/ARCHITECTURE.md +601 -0
  37. agctl-0.1.0/docs/DESIGN.md +1771 -0
  38. agctl-0.1.0/pyproject.toml +69 -0
  39. agctl-0.1.0/tests/__init__.py +0 -0
  40. agctl-0.1.0/tests/fixtures/agctl.yaml +117 -0
  41. agctl-0.1.0/tests/integration/__init__.py +0 -0
  42. agctl-0.1.0/tests/integration/conftest.py +280 -0
  43. agctl-0.1.0/tests/integration/test_db_commands.py +61 -0
  44. agctl-0.1.0/tests/integration/test_http_commands.py +75 -0
  45. agctl-0.1.0/tests/integration/test_kafka_commands.py +91 -0
  46. agctl-0.1.0/tests/unit/__init__.py +0 -0
  47. agctl-0.1.0/tests/unit/test_assertion_registry.py +221 -0
  48. agctl-0.1.0/tests/unit/test_assertions.py +205 -0
  49. agctl-0.1.0/tests/unit/test_check_commands.py +246 -0
  50. agctl-0.1.0/tests/unit/test_cli.py +137 -0
  51. agctl-0.1.0/tests/unit/test_command.py +97 -0
  52. agctl-0.1.0/tests/unit/test_db_client.py +116 -0
  53. agctl-0.1.0/tests/unit/test_db_commands.py +567 -0
  54. agctl-0.1.0/tests/unit/test_discover_command.py +235 -0
  55. agctl-0.1.0/tests/unit/test_discovery.py +47 -0
  56. agctl-0.1.0/tests/unit/test_errors.py +80 -0
  57. agctl-0.1.0/tests/unit/test_http_client.py +128 -0
  58. agctl-0.1.0/tests/unit/test_http_commands.py +257 -0
  59. agctl-0.1.0/tests/unit/test_http_ping.py +520 -0
  60. agctl-0.1.0/tests/unit/test_interpolation.py +41 -0
  61. agctl-0.1.0/tests/unit/test_kafka_client.py +597 -0
  62. agctl-0.1.0/tests/unit/test_kafka_commands.py +952 -0
  63. agctl-0.1.0/tests/unit/test_loader.py +151 -0
  64. agctl-0.1.0/tests/unit/test_models.py +64 -0
  65. agctl-0.1.0/tests/unit/test_output.py +35 -0
  66. agctl-0.1.0/tests/unit/test_params.py +33 -0
  67. agctl-0.1.0/tests/unit/test_plugins.py +235 -0
  68. agctl-0.1.0/tests/unit/test_postgresql_driver.py +183 -0
  69. agctl-0.1.0/tests/unit/test_resolution.py +141 -0
  70. agctl-0.1.0/tests/unit/test_resolver.py +70 -0
  71. agctl-0.1.0/tests/unit/test_smoke.py +4 -0
  72. agctl-0.1.0/tests/unit/test_validator.py +173 -0
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: docs-watcher
3
+ description: Review code/config changes and decide whether DESIGN.md or ARCHITECTURE.md need syncing at their respective altitudes.
4
+ tools: Read, Grep, Glob, Edit, Write, Bash
5
+ model: sonnet
6
+ ---
7
+
8
+ # docs-watcher Subagent
9
+
10
+ You are the `docs-watcher` subagent for the `agctl` project. Your job is to review code and configuration changes, then decide whether the project documentation needs updating.
11
+
12
+ ## The Two Documents and Their Altitudes
13
+
14
+ You are responsible for two documents that live at **different abstraction altitudes**. Preserving this distinction is your core responsibility.
15
+
16
+ ### `/Users/dmitry/Desktop/CursorProjects/agenttest/docs/DESIGN.md`
17
+ **Altitude:** WHAT and WHY — design-level, user-facing contract.
18
+
19
+ Contains:
20
+ - Goals and non-goals (§1)
21
+ - Configuration schema — what fields exist, what they mean (§2)
22
+ - CLI command surface — flags, arguments, behavior (§3)
23
+ - Output schema — JSON structure, error types (§4)
24
+ - Config resolution order (§5)
25
+ - Extension contracts — what plugins/assertions can do (§9)
26
+ - Roadmap/future work (§10)
27
+
28
+ **What does NOT belong here:** Implementation mechanics, module layouts, internal data flows, how the code actually works.
29
+
30
+ ### `/Users/dmitry/Desktop/CursorProjects/agenttest/docs/ARCHITECTURE.md`
31
+ **Altitude:** HOW — implementation-level, as-built source of truth.
32
+
33
+ Contains:
34
+ - Module & layer map — file tree, responsibility per module (§3)
35
+ - Request lifecycle — step-by-step runtime flow (§4)
36
+ - Configuration pipeline — how load/validate/resolve works (§5)
37
+ - Transport/client internals — lazy imports, exception mappings (§8)
38
+ - Testing architecture — unit vs integration, fixtures (§12)
39
+ - Design-vs-implementation deltas — where code diverges from DESIGN (§14)
40
+
41
+ **What does NOT belong here:** User-facing behavior changes that are spec-level, not implementation-level.
42
+
43
+ ## Your Decision Process
44
+
45
+ For every code/config change, you MUST:
46
+
47
+ 1. **Read what changed** — Use `git status` and `git diff` (against the appropriate base) to understand what materially changed in behavior or structure.
48
+
49
+ 2. **Classify the change:**
50
+ - **(a) User-facing behavior/contract change** — new/changed CLI flags, config schema fields, output schema, error types, extension contracts.
51
+ - **(b) Internal structural/architectural change** — module layout, runtime flow, internal mechanisms, packaging, testing architecture.
52
+ - **(c) Trivial/cosmetic/refactor-with-no-behavior-change** — test additions, formatting, behavior-preserving refactors.
53
+
54
+ 3. **For each doc, ask:** Does this change fall within this doc's SCOPE **and** ALTITUDE?
55
+ - DESIGN.md: only user-facing contract changes (type a).
56
+ - ARCHITECTURE.md: internal structural changes (type b).
57
+
58
+ 4. **Decide:**
59
+ - If the change belongs in a doc AT ITS ALTITUDE and is IMPORTANT → update that doc, matching its existing style, terseness, and detail level exactly. Edit only the relevant lines; do not expand the section.
60
+ - If the change has NO doc at this granularity (e.g., a pure internal helper, a test addition, a behavior-preserving refactor), OR is trivial, OR sits below the doc's altitude → **DO NOT update. A correct no-op is better than a speculative edit.**
61
+
62
+ 5. **Default to leaving docs untouched.** When unsure, do not edit — and say so.
63
+
64
+ ## Your Rules
65
+
66
+ 1. **NEVER change a doc's altitude.** Do not inject low-level implementation detail into DESIGN.md. Do not strip detail from ARCHITECTURE.md.
67
+
68
+ 2. **NEVER invent new sections.** If a change has no natural home in an existing section, it does not belong in that doc.
69
+
70
+ 3. **Match existing style exactly.** When you do edit, preserve the doc's voice, terseness, and level of detail. Do not expand a section just because you can.
71
+
72
+ 4. **Report transparently.** ALWAYS end by reporting:
73
+ - What you reviewed
74
+ - What you changed (one-line reason per change)
75
+ - What you deliberately did NOT change (and why)
76
+
77
+ 5. **Git is your source of truth.** Use `git diff` to see what actually changed. Do not speculate from file names alone.
78
+
79
+ ## Example Workflow
80
+
81
+ 1. Run `git status` to see what files changed.
82
+ 2. Run `git diff <base> -- <files>` to read the actual changes.
83
+ 3. Classify each change per step 2 above.
84
+ 4. For each doc, ask the scope+altitude question.
85
+ 5. Make edits ONLY when the answer is "yes, at this altitude, and important."
86
+ 6. Report your findings.
87
+
88
+ ## What You Do NOT Do
89
+
90
+ - Do NOT update docs for test additions or test-only changes.
91
+ - Do NOT update docs for cosmetic refactorings (renames, formatting) that preserve behavior.
92
+ - Do NOT update docs for internal helpers that aren't user-visible.
93
+ - Do NOT "cover" a change by inventing a new section.
94
+ - Do NOT silently edit — always report what you did and why.
agctl-0.1.0/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ # macOS
2
+ .DS_Store
3
+
4
+ # Claude Code local state (project agents/ and shared settings.json ARE tracked)
5
+ .claude/settings.local.json
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *.egg-info/
11
+ .eggs/
12
+ build/
13
+ dist/
14
+ .venv/
15
+ venv/
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+
20
+ # env files (keep .env.example tracked)
21
+ .env
22
+ .env.local
agctl-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,13 @@
1
+ # agctl
2
+
3
+ Agentic CLI interface for system under test.
4
+
5
+ ## Docs
6
+
7
+ [docs/](./docs) - documentation
8
+ - [DESIGN.md](./docs/DESIGN.md) - project design doc (intent & spec)
9
+ - [ARCHITECTURE.md](./docs/ARCHITECTURE.md) - as-built architecture (source of truth: module layout, runtime flow, extension points)
10
+
11
+ ## Docs Sync
12
+
13
+ After completing any task that changes code or config, invoke the `docs-watcher` subagent to check whether DESIGN.md or ARCHITECTURE.md need syncing. The subagent preserves each doc's altitude: DESIGN.md captures user-facing contract (WHAT/WHY), ARCHITECTURE.md captures as-built implementation (HOW). A correct no-op is better than a speculative edit.
agctl-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dmitry Teryaev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
agctl-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,414 @@
1
+ Metadata-Version: 2.4
2
+ Name: agctl
3
+ Version: 0.1.0
4
+ Summary: Agent-facing CLI harness for testing distributed systems
5
+ Project-URL: Homepage, https://github.com/HumanBean17/agctl
6
+ Project-URL: Repository, https://github.com/HumanBean17/agctl
7
+ Project-URL: Issues, https://github.com/HumanBean17/agctl/issues
8
+ Project-URL: Changelog, https://github.com/HumanBean17/agctl/releases
9
+ Author-email: Dmitry Teryaev <doudmitry@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,cli,database,distributed-systems,http,kafka,testing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: click>=8.1
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Provides-Extra: db
27
+ Requires-Dist: jq>=1.6; extra == 'db'
28
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'db'
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Provides-Extra: http
32
+ Requires-Dist: httpx>=0.27; extra == 'http'
33
+ Provides-Extra: integration
34
+ Requires-Dist: confluent-kafka>=2.4; extra == 'integration'
35
+ Requires-Dist: httpx>=0.27; extra == 'integration'
36
+ Requires-Dist: jq>=1.6; extra == 'integration'
37
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'integration'
38
+ Requires-Dist: pytest>=8.0; extra == 'integration'
39
+ Requires-Dist: testcontainers; extra == 'integration'
40
+ Provides-Extra: kafka
41
+ Requires-Dist: confluent-kafka>=2.4; extra == 'kafka'
42
+ Requires-Dist: jq>=1.6; extra == 'kafka'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # agctl
46
+
47
+ > Agent-facing CLI harness for testing distributed systems.
48
+
49
+ `agctl` (alias: `agt`) is a small, system-agnostic command-line tool that an AI
50
+ coding agent drives to verify a running system. It talks **HTTP**, **Kafka**, and
51
+ **databases**, and gives the agent one consistent contract for all of them: every
52
+ invocation prints exactly **one JSON object** on stdout and exits with a
53
+ deterministic code (`0` success, `1` assertion failed, `2` config/tool/env error).
54
+
55
+ ```
56
+ $ agctl http call create-order --param customer_id=cust-42 --param sku=WIDGET-001
57
+ {"ok": true, "command": "http.call", "result": {"status_code": 201, ...}, "error": null, "duration_ms": 87}
58
+ ```
59
+
60
+ ## Why it exists
61
+
62
+ Agents need **deterministic, machine-readable feedback** to know whether a change
63
+ worked. Raw `curl` output, prose logs, and non-zero exit codes from shell glue are
64
+ noisy and ambiguous — an agent can't reliably tell "the feature is broken" from
65
+ "my command had a typo."
66
+
67
+ `agctl` closes that gap. It is a **harness** built specifically to be driven by an
68
+ agent (humans and CI can use it too):
69
+
70
+ - **One object, one code.** A single JSON envelope + a strict exit code, on every
71
+ command, across every protocol. Parse `ok`/`result`/`error` and move on.
72
+ - **Composable, narrow commands.** The agent chains them instead of relying on a
73
+ monolithic "run scenario" command: send a request → assert a Kafka event →
74
+ assert a DB row.
75
+ - **System-agnostic.** The tool ships with zero knowledge of your project. All
76
+ endpoints, topics, connections, SQL, and request templates live in an
77
+ `agctl.yaml` you commit to your repo.
78
+ - **Fail loudly.** A wrong assertion always exits `1`. There is no silent
79
+ false-positive — the worst possible failure mode for an agent harness.
80
+ - **Discovery, not dumps.** A three-level `discover` command lets the agent learn
81
+ what your system offers *without* loading your entire config into its context.
82
+
83
+ Design intent and the full spec live in [`docs/DESIGN.md`](./docs/DESIGN.md); the
84
+ as-built architecture (module layout, runtime flow, extension points) is the
85
+ source of truth in [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md).
86
+
87
+ ---
88
+
89
+ ## Setup
90
+
91
+ **Requirements:** Python ≥ 3.11.
92
+
93
+ Install `agctl` into your project (this repo uses `uv` — `pip` works too):
94
+
95
+ ```bash
96
+ # with uv (recommended — this repo ships a uv.lock)
97
+ uv pip install -e .
98
+
99
+ # or with pip
100
+ pip install -e .
101
+ ```
102
+
103
+ The core install pulls only `click`, `pyyaml`, and `pydantic`. Protocol libraries
104
+ are **optional extras** — install only what your system needs. Heavy libs are
105
+ lazy-imported, so a missing extra fails with a clear `ConfigError` (exit 2) rather
106
+ than a crash:
107
+
108
+ ```bash
109
+ pip install -e ".[http]" # http call / request / ping / check ready
110
+ pip install -e ".[kafka]" # kafka produce / consume / assert
111
+ pip install -e ".[db]" # db query / assert
112
+ pip install -e ".[http,kafka,db]" # everything (typical)
113
+ ```
114
+
115
+ Verify the install — both binary names work:
116
+
117
+ ```bash
118
+ agctl --help
119
+ agt --help
120
+ ```
121
+
122
+ Drop a config file named `agctl.yaml` at your repo root (or anywhere up the
123
+ directory tree). Confirm it loads and validates:
124
+
125
+ ```bash
126
+ agctl config validate
127
+ ```
128
+
129
+ Then orient yourself — this is the first command an agent runs in a session:
130
+
131
+ ```bash
132
+ agctl discover
133
+ ```
134
+
135
+ ---
136
+
137
+ ## CLI abilities
138
+
139
+ `--config <path>` is a global flag on every command (otherwise `agctl.yaml` is
140
+ auto-discovered from the current directory upward).
141
+
142
+ | Group | Command | What it does |
143
+ |---|---|---|
144
+ | **`http`** | `call <template>` | Execute a named HTTP template from config |
145
+ | | `request` | Free-form request (escape hatch; `--service --method --path`) |
146
+ | | `ping <template>` | Repeat a request on an interval — stream NDJSON (session keepalive) |
147
+ | **`kafka`** | `produce` | Publish one message (`--topic --message`) |
148
+ | | `consume` | Read a topic; return up to `--expect-count` matches within `--timeout` |
149
+ | | `assert` | Fail (exit 1) unless a matching message arrives within `--timeout`. Modes: `--contains`, `--match <jq>`, `--pattern <name>` (combinable) |
150
+ | **`db`** | `query` | Run `--template` or free-form `--sql`; return all rows |
151
+ | | `assert` | Assert `--expect-rows N`, or `--expect-value --path <jq> --equals <v>` on the first row |
152
+ | **`check`** | `ready` | Hit `health_path` for one (`--service`) or all services; 2xx = ready |
153
+ | **`config`** | `validate` | Validate schema, env vars, cross-references, version |
154
+ | | `show` | Dump fully-resolved config as JSON (secrets masked) |
155
+ | **`discover`** | *(top-level)* | Three levels: summary → `--category` → `--name`; plus `--search` |
156
+
157
+ **Composing commands** — the core pattern is *send, then assert*:
158
+
159
+ ```bash
160
+ # 1. Trigger an action
161
+ agctl http call create-order --param customer_id=cust-42 --param sku=WIDGET-001
162
+
163
+ # 2. Assert the downstream Kafka event arrived (reliable by default — see notes)
164
+ agctl kafka assert --topic orders.created --contains '{"customer_id": "cust-42"}' --timeout 10
165
+
166
+ # 3. Assert the DB reflects the new state
167
+ agctl db assert --template find-order --param orderId=ord-789 --expect-value \
168
+ --path ".status" --equals "PENDING"
169
+ ```
170
+
171
+ **Exit codes** (the contract the agent relies on):
172
+
173
+ | Code | Meaning |
174
+ |---|---|
175
+ | `0` | Success; all assertions passed |
176
+ | `1` | An assertion was evaluated and **failed** — the system is not in the expected state |
177
+ | `2` | Tool/config/env error — not an assertion result; fix the invocation or environment |
178
+
179
+ > **Send-then-assert is reliable by default.** `kafka consume`/`assert` seek each
180
+ > partition to `now - --lookback` (default `= --timeout`) and read forward — they
181
+ > do **not** subscribe at "latest" — so an event published a moment before the
182
+ > command starts still falls inside the window.
183
+
184
+ See [`docs/DESIGN.md` §3](./docs/DESIGN.md) for the complete flag reference and
185
+ [`docs/DESIGN.md` §11](./docs/DESIGN.md) for end-to-end agentic workflow examples.
186
+
187
+ ---
188
+
189
+ ## Configuration
190
+
191
+ `agctl` loads one `agctl.yaml` per invocation. Resolution order (highest first):
192
+
193
+ 1. `--config <path>` — if given, *only* this file is loaded.
194
+ 2. `AGCTL_CONFIG` — env var pointing at the config file.
195
+ 3. **Walk-up discovery** — searches from the current directory upward for
196
+ `agctl.yaml`, stopping at the first `.git` or the filesystem root.
197
+ 4. **`${ENV_VAR}` interpolation** in YAML string values (after parsing).
198
+ 5. **`AGCTL_<SECTION>__<KEY>` overrides** — highest precedence; applied last.
199
+
200
+ If no file is found, it exits `2` with a `ConfigError`.
201
+
202
+ ### Env-var interpolation (in any string value)
203
+
204
+ | Syntax | Behavior |
205
+ |---|---|
206
+ | `${VAR}` | **Required.** Missing → `ConfigError` (exit 2), never a silent empty string. |
207
+ | `${VAR:-default}` | Optional with a literal default. |
208
+ | `${VAR:-}` | Optional; missing → empty string. |
209
+
210
+ Three substitution syntaxes exist — don't conflate them:
211
+
212
+ - `${VAR}` — environment, resolved at config load.
213
+ - `{name}` — HTTP path/body & Kafka-pattern placeholders, filled at call time from `--param key=value`.
214
+ - `:name` — JDBC-style SQL params (templates and free-form `--sql`), bound at execute time.
215
+
216
+ ### Env-var overrides
217
+
218
+ `AGCTL_<SECTION>__<KEY>=value` (double underscore separates path segments;
219
+ uppercase each segment; hyphens → `_` within a segment). Applied **after**
220
+ interpolation, with highest precedence:
221
+
222
+ ```bash
223
+ AGCTL_DEFAULTS__TIMEOUT_SECONDS=30
224
+ AGCTL_KAFKA__DEFAULT_CONSUMER_GROUP=ci-consumer
225
+ AGCTL_DATABASE__CONNECTIONS__MAIN_DB__HOST=localhost
226
+ AGCTL_DATABASE__CONNECTIONS__MAIN_DB__PASSWORD=supersecret
227
+ AGCTL_SERVICES__ORDER_SERVICE__BASE_URL=http://order-svc:8080
228
+ ```
229
+
230
+ ### Complete, copy-paste-ready config
231
+
232
+ Below is a full `agctl.yaml` with **concrete localhost values and no required env
233
+ vars** — drop it at your repo root and `agctl config validate` passes as-is. (The
234
+ production version is the same file with secrets/hosts moved into `${...}` and
235
+ sourced from a `.env` — see the note after it.)
236
+
237
+ ```yaml
238
+ # agctl.yaml
239
+ # Version tracks the agctl MAJOR version only (currently "1").
240
+ version: "1"
241
+
242
+ # --- services: named HTTP base URLs for services under test -----------------
243
+ services:
244
+ order-service:
245
+ base_url: "http://localhost:8081"
246
+ health_path: "/actuator/health" # used by `agctl check ready`
247
+ timeout_seconds: 10 # optional; overrides defaults.timeout_seconds
248
+
249
+ payment-service:
250
+ base_url: "http://localhost:8082"
251
+ health_path: "/health"
252
+ timeout_seconds: 15
253
+
254
+ # --- kafka: broker config --------------------------------------------------
255
+ kafka:
256
+ brokers:
257
+ - "localhost:9092"
258
+ default_consumer_group: "agctl-consumer"
259
+ schema_registry_url: "" # optional; omit/leave empty if unused
260
+ timeout_seconds: 30 # default consume/assert timeout
261
+
262
+ # Optional TLS/mTLS — uncomment for brokers that require SSL. Setting ANY
263
+ # field to a non-empty value enables TLS (security.protocol defaults to "SSL").
264
+ # ca_location is optional: unset → librdkafka uses the system trust store
265
+ # (fine for publicly-trusted brokers; pin a CA for private-PKI brokers).
266
+ # Hostname verification stays ON unless endpoint_identification_algorithm: "none".
267
+ # ssl:
268
+ # ca_location: ""
269
+ # certificate_location: "" # path to client cert (mTLS)
270
+ # key_location: "" # path to client private key (mTLS)
271
+ # key_password: "" # optional private-key password
272
+ # # endpoint_identification_algorithm: "none" # disable hostname verification
273
+ # # security_protocol: "SSL" # default; set SASL_SSL when adding SASL
274
+
275
+ # patterns: named Kafka filters, analogous to HTTP templates.
276
+ # topic: Kafka topic
277
+ # match: jq boolean predicate over each message value;
278
+ # supports {placeholder} substitution via --param at assert time
279
+ patterns:
280
+ order-created:
281
+ description: "An ORDER_CREATED event for a specific order"
282
+ topic: orders.created
283
+ match: '.eventType == "ORDER_CREATED" and .payload.orderId == "{orderId}"'
284
+
285
+ payment-failed:
286
+ description: "Any PAYMENT_FAILED event regardless of order"
287
+ topic: payments.events
288
+ match: '.eventType == "PAYMENT_FAILED"'
289
+
290
+ # --- database: named connection profiles and SQL templates ------------------
291
+ database:
292
+ connections:
293
+ main-db:
294
+ type: postgresql # extensible via plugins (entry point agctl.db_drivers)
295
+ host: "localhost"
296
+ port: 5432
297
+ dbname: "app"
298
+ user: "app"
299
+ password: "app"
300
+ default: true # used when --connection is omitted
301
+
302
+ analytics-db:
303
+ type: postgresql
304
+ host: "localhost"
305
+ port: 5432
306
+ dbname: "analytics"
307
+ user: "analytics"
308
+ password: "analytics"
309
+
310
+ # templates: named SQL queries. `connection` is optional (falls back to
311
+ # defaults.database_connection). Use :paramName named params (JDBC-style).
312
+ templates:
313
+ find-order:
314
+ description: "Fetch a single order by ID"
315
+ connection: main-db
316
+ sql: "SELECT id, status, total_cents, created_at FROM orders WHERE id = :orderId"
317
+
318
+ orders-by-status:
319
+ description: "List orders in a given status, optionally filtered by customer"
320
+ connection: main-db
321
+ sql: "SELECT id, status FROM orders WHERE status = :status AND customer_id = :customerId"
322
+
323
+ count-failed-payments:
324
+ description: "Count failed payments after a given timestamp"
325
+ connection: main-db
326
+ sql: "SELECT COUNT(*) AS cnt FROM payments WHERE status = 'FAILED' AND created_at > :since"
327
+
328
+ # --- templates: named HTTP request templates --------------------------------
329
+ templates:
330
+ create-order:
331
+ description: "Submit a new order for a customer"
332
+ method: POST
333
+ service: order-service
334
+ path: "/api/v1/orders"
335
+ headers:
336
+ Content-Type: "application/json"
337
+ X-Request-Source: "agctl"
338
+ body:
339
+ customer_id: "{customer_id}"
340
+ items:
341
+ - sku: "{sku}"
342
+ quantity: 1
343
+
344
+ get-order:
345
+ description: "Fetch a single order by ID"
346
+ method: GET
347
+ service: order-service
348
+ path: "/api/v1/orders/{order_id}"
349
+
350
+ charge-payment:
351
+ description: "Trigger payment charge for an order"
352
+ method: POST
353
+ service: payment-service
354
+ path: "/api/v1/payments"
355
+ headers:
356
+ Content-Type: "application/json"
357
+ Authorization: "Bearer ${PAYMENT_SERVICE_TOKEN}" # required env var
358
+ body:
359
+ order_id: "{order_id}"
360
+ amount_cents: "{amount_cents}"
361
+
362
+ get-payment-status:
363
+ description: "Fetch payment status by order ID"
364
+ method: GET
365
+ service: payment-service
366
+ path: "/api/v1/payments/{order_id}/status"
367
+
368
+ # --- defaults: project-wide fallbacks --------------------------------------
369
+ defaults:
370
+ timeout_seconds: 10
371
+ database_connection: main-db
372
+ ```
373
+
374
+ > **Note:** `charge-payment` references `${PAYMENT_SERVICE_TOKEN}` (a *required*
375
+ > env var), so `config validate` will report it as unresolved until you export it.
376
+ > Either `export PAYMENT_SERVICE_TOKEN=...` or remove that template for a clean
377
+ > baseline.
378
+
379
+ **Moving to environment-driven config** — replace the concrete values above with
380
+ `${...}` and source them from a `.env` (agctl resolves them at load time):
381
+
382
+ ```bash
383
+ # .env — never commit real secrets
384
+ ORDER_SERVICE_URL=http://order-svc:8081
385
+ PAYMENT_SERVICE_URL=http://payment-svc:8082
386
+ KAFKA_BROKER=kafka:9092
387
+ DB_HOST=postgres
388
+ DB_NAME=app
389
+ DB_USER=app
390
+ DB_PASSWORD=change-me
391
+ PAYMENT_SERVICE_TOKEN=change-me
392
+ ```
393
+
394
+ ```yaml
395
+ # agctl.yaml (snippet) — same file, env-interpolated
396
+ services:
397
+ order-service:
398
+ base_url: "${ORDER_SERVICE_URL}"
399
+ database:
400
+ connections:
401
+ main-db:
402
+ host: "${DB_HOST}"
403
+ dbname: "${DB_NAME}"
404
+ password: "${DB_PASSWORD}"
405
+ port: "${DB_PORT:-5432}" # optional-with-default form
406
+ ```
407
+
408
+ Validate before committing, and use `config show` to inspect the resolved result
409
+ (secrets are masked; pass `--unmask` only in trusted environments):
410
+
411
+ ```bash
412
+ agctl config validate
413
+ agctl config show
414
+ ```