harnessweaver-cli 0.7.1__tar.gz → 0.7.2__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 (96) hide show
  1. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/CHANGELOG.md +10 -0
  2. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/PKG-INFO +9 -6
  3. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/README.md +8 -5
  4. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/pyproject.toml +1 -1
  5. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/scaffold.py +5 -2
  6. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/README.md +6 -5
  7. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/app/main.py +2 -0
  8. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/README.md +5 -4
  9. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/app/main.py +2 -1
  10. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/README.md +6 -5
  11. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/app/main.py +2 -0
  12. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/README.md +5 -4
  13. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/app/main.py +2 -0
  14. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/README.md +5 -4
  15. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/app/main.py +2 -0
  16. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_init.py +59 -2
  17. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/.gitignore +0 -0
  18. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/LICENSE +0 -0
  19. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/__init__.py +0 -0
  20. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/_load.py +0 -0
  21. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/_yaml.py +0 -0
  22. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/add.py +0 -0
  23. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/dev.py +0 -0
  24. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/doctor.py +0 -0
  25. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/evals.py +0 -0
  26. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/gateway.py +0 -0
  27. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/main.py +0 -0
  28. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/plugins.py +0 -0
  29. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/policy.py +0 -0
  30. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/py.typed +0 -0
  31. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/replay.py +0 -0
  32. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/sidecar.py +0 -0
  33. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/spec.py +0 -0
  34. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_dev/compose.yaml +0 -0
  35. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_dev/otel-collector.yaml +0 -0
  36. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_plugin/README.md +0 -0
  37. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_plugin/pyproject.toml +0 -0
  38. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_plugin/src/__ns___harness/__init__.py +0 -0
  39. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_plugin/src/__ns___harness/guards.py +0 -0
  40. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_plugin/src/__ns___harness/py.typed +0 -0
  41. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/_plugin/tests/test_guards.py +0 -0
  42. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/app/__init__.py +0 -0
  43. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/app/agent.py +0 -0
  44. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/app/tools.py +0 -0
  45. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/evals/redteam.yaml +0 -0
  46. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/harness.offline.yaml +0 -0
  47. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/harness.yaml +0 -0
  48. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/pyproject.toml +0 -0
  49. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/tests/test_agent.py +0 -0
  50. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/tools/lookup_order.tool.yaml +0 -0
  51. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/agent/tools/refund_payment.tool.yaml +0 -0
  52. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/app/__init__.py +0 -0
  53. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/app/chat.py +0 -0
  54. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/evals/golden.yaml +0 -0
  55. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/harness.offline.yaml +0 -0
  56. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/harness.yaml +0 -0
  57. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/pyproject.toml +0 -0
  58. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/chat/tests/test_chat.py +0 -0
  59. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/app/__init__.py +0 -0
  60. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/app/models.py +0 -0
  61. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/evals/golden.yaml +0 -0
  62. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/harness.offline.yaml +0 -0
  63. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/harness.yaml +0 -0
  64. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/prompts/extract@1.md +0 -0
  65. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/pyproject.toml +0 -0
  66. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/extraction/tests/test_extract.py +0 -0
  67. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/app/__init__.py +0 -0
  68. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/app/flow.py +0 -0
  69. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/data/kb.yaml +0 -0
  70. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/evals/golden.yaml +0 -0
  71. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/harness.offline.yaml +0 -0
  72. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/harness.yaml +0 -0
  73. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/prompts/answer@1.md +0 -0
  74. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/pyproject.toml +0 -0
  75. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/rag/tests/test_answer.py +0 -0
  76. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/app/__init__.py +0 -0
  77. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/app/steps.py +0 -0
  78. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/evals/golden.yaml +0 -0
  79. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/harness.offline.yaml +0 -0
  80. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/harness.yaml +0 -0
  81. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/prompts/classify@1.md +0 -0
  82. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/prompts/reply@1.md +0 -0
  83. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/prompts/summarize@1.md +0 -0
  84. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/pyproject.toml +0 -0
  85. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/templates/workflow/tests/test_pipeline.py +0 -0
  86. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/src/harnessweaver_cli/tools.py +0 -0
  87. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/conftest.py +0 -0
  88. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_add_and_tools.py +0 -0
  89. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_cli.py +0 -0
  90. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_dev.py +0 -0
  91. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_doctor.py +0 -0
  92. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_evals.py +0 -0
  93. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_gateway.py +0 -0
  94. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_plugins.py +0 -0
  95. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_policy_and_replay.py +0 -0
  96. {harnessweaver_cli-0.7.1 → harnessweaver_cli-0.7.2}/tests/test_spec.py +0 -0
@@ -4,6 +4,16 @@ All notable changes to the `harnessweaver` command and its project templates. Th
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## 0.7.2 - 2026-11-08
8
+
9
+ ### Changed
10
+
11
+ - `harnessweaver init` ends with next steps that work right after `uvx --from harnessweaver-cli harnessweaver init`, with nothing installed yet: `uv sync`, then `uv run harnessweaver spec validate --env offline` and `uv run harnessweaver test`. The templates' READMEs run their commands the same way, and the package's README uses absolute links, which work on PyPI.
12
+
13
+ ### Fixed
14
+
15
+ - A template's `app/main.py` now prints why a run didn't complete, as `error: <code>: <message>` (for example `error: provider_error: openai.chat needs $LITELLM_API_KEY`), and the `chat` template's terminal app shows the reason next to the status. A first run against a real model with no key or no gateway printed only `status: failed` and `output: None`.
16
+
7
17
  ## 0.7.1 - 2026-11-01
8
18
 
9
19
  The first version published to PyPI.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: harnessweaver-cli
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: Developer tooling for HarnessWeaver: the harnessweaver command.
5
5
  Author-email: Muthu Kumaran R <rsmk7new@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -23,22 +23,25 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  # harnessweaver-cli
25
25
 
26
- Developer tooling for [HarnessWeaver](../../README.md). Installs the `harnessweaver` command. The full reference, with every option and exit code, is [docs/cli.md](../../docs/cli.md).
26
+ Developer tooling for [HarnessWeaver](https://github.com/Wise-DreamerZ/HarnessWeaver). Installs the `harnessweaver` command. The full reference, with every option and exit code, is [the CLI page](https://harnessweaver.wisedreamerz.com/docs/cli/) of the docs.
27
27
 
28
28
  ```bash
29
29
  uv add --dev harnessweaver-cli
30
- harnessweaver --help
30
+ uv run harnessweaver --help
31
31
  ```
32
32
 
33
33
  ## Start a project
34
34
 
35
35
  ```bash
36
- harnessweaver init invoice-bot # the extraction template; -t picks another
36
+ uvx --from harnessweaver-cli harnessweaver init invoice-bot # the extraction template; -t picks another
37
37
  cd invoice-bot
38
- harnessweaver spec validate --env offline # load it the way the app will
39
- harnessweaver test # its tests, offline
38
+ uv sync # the project's own dependencies, the CLI among them
39
+ uv run harnessweaver spec validate --env offline # load it the way the app will
40
+ uv run harnessweaver test # its tests, offline
40
41
  ```
41
42
 
43
+ `uvx` runs the command once without installing it. A new project lists `harnessweaver-cli` among its development dependencies, so from then on `uv run harnessweaver` is the project's own copy.
44
+
42
45
  `init` copies a template and puts the project name in it; there is no template
43
46
  language. A plugin's templates are named `<namespace>.<template>`
44
47
  (`harnessweaver init -t acme.rag`). Every template's output passes its own tests
@@ -1,21 +1,24 @@
1
1
  # harnessweaver-cli
2
2
 
3
- Developer tooling for [HarnessWeaver](../../README.md). Installs the `harnessweaver` command. The full reference, with every option and exit code, is [docs/cli.md](../../docs/cli.md).
3
+ Developer tooling for [HarnessWeaver](https://github.com/Wise-DreamerZ/HarnessWeaver). Installs the `harnessweaver` command. The full reference, with every option and exit code, is [the CLI page](https://harnessweaver.wisedreamerz.com/docs/cli/) of the docs.
4
4
 
5
5
  ```bash
6
6
  uv add --dev harnessweaver-cli
7
- harnessweaver --help
7
+ uv run harnessweaver --help
8
8
  ```
9
9
 
10
10
  ## Start a project
11
11
 
12
12
  ```bash
13
- harnessweaver init invoice-bot # the extraction template; -t picks another
13
+ uvx --from harnessweaver-cli harnessweaver init invoice-bot # the extraction template; -t picks another
14
14
  cd invoice-bot
15
- harnessweaver spec validate --env offline # load it the way the app will
16
- harnessweaver test # its tests, offline
15
+ uv sync # the project's own dependencies, the CLI among them
16
+ uv run harnessweaver spec validate --env offline # load it the way the app will
17
+ uv run harnessweaver test # its tests, offline
17
18
  ```
18
19
 
20
+ `uvx` runs the command once without installing it. A new project lists `harnessweaver-cli` among its development dependencies, so from then on `uv run harnessweaver` is the project's own copy.
21
+
19
22
  `init` copies a template and puts the project name in it; there is no template
20
23
  language. A plugin's templates are named `<namespace>.<template>`
21
24
  (`harnessweaver init -t acme.rag`). Every template's output passes its own tests
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "harnessweaver-cli"
7
- version = "0.7.1"
7
+ version = "0.7.2"
8
8
  description = "Developer tooling for HarnessWeaver: the harnessweaver command."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -137,10 +137,13 @@ def init(
137
137
  raise fail(f"invalid project name {app_name!r}: use lowercase letters, digits and dashes")
138
138
  written = render_template(template, directory, app_name, force=force)
139
139
  typer.echo(f"created {len(written)} files in {directory} from the {template} template")
140
+ # The project installs its own CLI, so these steps work right after
141
+ # `uvx --from harnessweaver-cli harnessweaver init`, with nothing on PATH yet.
140
142
  typer.echo("next:")
141
143
  typer.echo(f" cd {directory}")
142
- typer.echo(" harnessweaver spec validate --env offline")
143
- typer.echo(" harnessweaver test")
144
+ typer.echo(" uv sync")
145
+ typer.echo(" uv run harnessweaver spec validate --env offline")
146
+ typer.echo(" uv run harnessweaver test")
144
147
 
145
148
 
146
149
  def run_tests(
@@ -8,11 +8,12 @@ A support agent that looks up orders and issues refunds. A Pydantic AI agent run
8
8
  - `evals/redteam.yaml` is the red-team set: injected instructions and over-the-limit refunds are blocked, and a large refund waits for a person.
9
9
 
10
10
  ```bash
11
- harnessweaver spec validate --env offline # check the spec
12
- harnessweaver test # run the tests offline
13
- harnessweaver eval run --env offline # grade the red-team set
14
- harnessweaver policy test # once you write policy_tests.yaml
15
- HARNESSWEAVER_ENV=offline python -m app.main # ask the agent once
11
+ uv sync # install the project and its tools
12
+ uv run harnessweaver spec validate --env offline # check the spec
13
+ uv run harnessweaver test # run the tests offline
14
+ uv run harnessweaver eval run --env offline # grade the red-team set
15
+ uv run harnessweaver policy test # once you write policy_tests.yaml
16
+ HARNESSWEAVER_ENV=offline uv run python -m app.main # ask the agent once
16
17
  ```
17
18
 
18
19
  Approve a waiting refund with `harness.approve(run_id, approval_id, approver=...)`; the agent continues from where it stopped, and only the approved call runs. After changing a tool's description or schema, review it and run `harnessweaver tools pin`.
@@ -13,6 +13,8 @@ async def main() -> None:
13
13
  result = await harness.run("support", "Where is my order A-1001?", principal="demo")
14
14
  print(f"status: {result.status.value}")
15
15
  print(f"output: {result.output}")
16
+ if result.error is not None: # why the run didn't complete
17
+ print(f"error: {result.error.code.value}: {result.error.message}")
16
18
 
17
19
 
18
20
  if __name__ == "__main__":
@@ -8,8 +8,9 @@ A chat assistant whose answers stream to the user, governed by HarnessWeaver.
8
8
  - `harness.offline.yaml` swaps the model for a scripted one that streams, so everything runs with no network.
9
9
 
10
10
  ```bash
11
- harnessweaver spec validate --env offline # check the spec
12
- harnessweaver test # run the tests offline
13
- harnessweaver eval run --env offline # grade evals/golden.yaml
14
- HARNESSWEAVER_ENV=offline python -m app.main # chat in the terminal
11
+ uv sync # install the project and its tools
12
+ uv run harnessweaver spec validate --env offline # check the spec
13
+ uv run harnessweaver test # run the tests offline
14
+ uv run harnessweaver eval run --env offline # grade evals/golden.yaml
15
+ HARNESSWEAVER_ENV=offline uv run python -m app.main # chat in the terminal
15
16
  ```
@@ -26,7 +26,8 @@ async def main() -> None:
26
26
  if event.type == "chunk" and event.chunk is not None:
27
27
  print(event.chunk.delta, end="", flush=True)
28
28
  result = await harness.result(run_id)
29
- print("" if result.ok else f"[{result.status.value}]")
29
+ why = f": {result.error.message}" if result.error is not None else ""
30
+ print("" if result.ok else f"[{result.status.value}{why}]")
30
31
 
31
32
 
32
33
  if __name__ == "__main__":
@@ -6,11 +6,12 @@ Typed extraction from customer support messages, governed by HarnessWeaver.
6
6
  - `harness.offline.yaml` swaps the model for a scripted one, so everything runs with no network.
7
7
 
8
8
  ```bash
9
- harnessweaver spec validate --env offline # check the spec
10
- harnessweaver doctor --env offline # adoption level and what to do next
11
- harnessweaver test # run the tests offline
12
- harnessweaver eval run --env offline # grade evals/golden.yaml; exits 1 on a regression
13
- HARNESSWEAVER_ENV=offline python -m app.main # run the task once
9
+ uv sync # install the project and its tools
10
+ uv run harnessweaver spec validate --env offline # check the spec
11
+ uv run harnessweaver doctor --env offline # adoption level and what to do next
12
+ uv run harnessweaver test # run the tests offline
13
+ uv run harnessweaver eval run --env offline # grade evals/golden.yaml; exits 1 on a regression
14
+ HARNESSWEAVER_ENV=offline uv run python -m app.main # run the task once
14
15
  ```
15
16
 
16
17
  To call a real model, run LiteLLM Proxy at `base_url`, set `LITELLM_API_KEY`, and map the `fast` alias to a model in `harness.yaml`.
@@ -15,6 +15,8 @@ async def main() -> None:
15
15
  result = await harness.run("extract", Message(text=SAMPLE), principal="demo")
16
16
  print(f"status: {result.status.value}")
17
17
  print(f"output: {result.output}")
18
+ if result.error is not None: # why the run didn't complete
19
+ print(f"error: {result.error.code.value}: {result.error.message}")
18
20
 
19
21
 
20
22
  if __name__ == "__main__":
@@ -8,10 +8,11 @@ Answers from a knowledge base, with sources, governed by HarnessWeaver.
8
8
  - `harness.offline.yaml` swaps the model for a scripted one, so everything runs with no network.
9
9
 
10
10
  ```bash
11
- harnessweaver spec validate --env offline # check the spec
12
- harnessweaver test # run the tests offline
13
- harnessweaver eval run --env offline # grade evals/golden.yaml
14
- HARNESSWEAVER_ENV=offline python -m app.main # answer one question
11
+ uv sync # install the project and its tools
12
+ uv run harnessweaver spec validate --env offline # check the spec
13
+ uv run harnessweaver test # run the tests offline
14
+ uv run harnessweaver eval run --env offline # grade evals/golden.yaml
15
+ HARNESSWEAVER_ENV=offline uv run python -m app.main # answer one question
15
16
  ```
16
17
 
17
18
  Add passages to `data/kb.yaml`, or replace the `memory` provider with a vector store plugin; the ACL re-check and the citation guard stay the same.
@@ -13,6 +13,8 @@ async def main() -> None:
13
13
  result = await harness.run("answer", "How long does a refund take?", principal="demo")
14
14
  print(f"status: {result.status.value}")
15
15
  print(f"output: {result.output}")
16
+ if result.error is not None: # why the run didn't complete
17
+ print(f"error: {result.error.code.value}: {result.error.message}")
16
18
 
17
19
 
18
20
  if __name__ == "__main__":
@@ -7,10 +7,11 @@ A support-ticket pipeline: clean the ticket, then classify and summarize it in p
7
7
  - `harness.offline.yaml` swaps the model for a scripted one whose first draft misses the category, so the review loop shows offline.
8
8
 
9
9
  ```bash
10
- harnessweaver spec validate --env offline # check the spec
11
- harnessweaver test # run the tests offline
12
- harnessweaver eval run --env offline # grade evals/golden.yaml
13
- HARNESSWEAVER_ENV=offline python -m app.main # triage one ticket
10
+ uv sync # install the project and its tools
11
+ uv run harnessweaver spec validate --env offline # check the spec
12
+ uv run harnessweaver test # run the tests offline
13
+ uv run harnessweaver eval run --env offline # grade evals/golden.yaml
14
+ HARNESSWEAVER_ENV=offline uv run python -m app.main # triage one ticket
14
15
  ```
15
16
 
16
17
  Finished steps are checkpointed: after an approval, only the interrupted step runs again.
@@ -14,6 +14,8 @@ async def main() -> None:
14
14
  result = await harness.run("triage", ticket, principal="demo")
15
15
  print(f"status: {result.status.value}")
16
16
  print(f"output: {result.output}")
17
+ if result.error is not None: # why the run didn't complete
18
+ print(f"error: {result.error.code.value}: {result.error.message}")
17
19
 
18
20
 
19
21
  if __name__ == "__main__":
@@ -1,7 +1,9 @@
1
1
  """``harnessweaver init`` and ``harnessweaver test``, including T6: a template's own tests pass."""
2
2
 
3
+ import importlib
3
4
  import sys
4
5
  from pathlib import Path
6
+ from types import ModuleType
5
7
 
6
8
  import pytest
7
9
  from typer.testing import CliRunner
@@ -9,7 +11,7 @@ from typer.testing import CliRunner
9
11
  import harnessweaver as hw
10
12
  from harnessweaver.core.registry import PluginCatalog, PluginSource
11
13
  from harnessweaver_cli.main import app
12
- from harnessweaver_cli.scaffold import BUILTIN_TEMPLATES
14
+ from harnessweaver_cli.scaffold import BUILTIN_TEMPLATES, render_template
13
15
 
14
16
  runner = CliRunner()
15
17
 
@@ -40,7 +42,62 @@ def test_init_renders_the_extraction_template(tmp_path: Path) -> None:
40
42
  assert "__app_name__" not in "".join(
41
43
  (target / f).read_text(encoding="utf-8") for f in files(target)
42
44
  )
43
- assert "harnessweaver spec validate --env offline" in result.output
45
+ # The next steps work right after `uvx ... init`, with nothing installed yet.
46
+ assert " uv sync\n" in result.output
47
+ assert " uv run harnessweaver spec validate --env offline\n" in result.output
48
+ assert " uv run harnessweaver test\n" in result.output
49
+
50
+
51
+ BROKEN = "models:\n provider: scripted\n replies: [{ fail: true }]\n"
52
+ """An overlay whose model fails every call, the way a missing key or gateway does."""
53
+
54
+
55
+ def forget_app() -> None:
56
+ """Drop the ``app`` modules, so the next import reads the current template's code."""
57
+ for name in [m for m in sys.modules if m == "app" or m.startswith("app.")]:
58
+ del sys.modules[name]
59
+
60
+
61
+ def broken_app(template: str, root: Path, monkeypatch: pytest.MonkeyPatch) -> ModuleType:
62
+ """Render ``template`` into ``root`` with a failing model, and import its ``app.main``."""
63
+ render_template(template, root, "demo")
64
+ (root / "harness.broken.yaml").write_text(BROKEN, encoding="utf-8")
65
+ monkeypatch.setenv("HARNESSWEAVER_ENV", "broken")
66
+ monkeypatch.syspath_prepend(str(root))
67
+ forget_app()
68
+ return importlib.import_module("app.main")
69
+
70
+
71
+ @pytest.mark.anyio
72
+ @pytest.mark.parametrize("template", ["extraction", "rag", "agent", "workflow"])
73
+ async def test_a_template_app_says_why_its_run_failed(
74
+ template: str,
75
+ tmp_path: Path,
76
+ monkeypatch: pytest.MonkeyPatch,
77
+ capsys: pytest.CaptureFixture[str],
78
+ ) -> None:
79
+ """A first run against a real model often fails (no key, no gateway): say why."""
80
+ try:
81
+ await broken_app(template, tmp_path / "demo", monkeypatch).main()
82
+ finally:
83
+ forget_app()
84
+ out = capsys.readouterr().out
85
+ assert "status: failed\n" in out
86
+ assert "error: provider_error: the script says this call fails\n" in out
87
+
88
+
89
+ @pytest.mark.anyio
90
+ async def test_the_chat_template_says_why_a_turn_failed(
91
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
92
+ ) -> None:
93
+ try:
94
+ main = broken_app("chat", tmp_path / "demo", monkeypatch)
95
+ messages = iter(["hello", ""])
96
+ monkeypatch.setattr(main, "ask", lambda: next(messages))
97
+ await main.main()
98
+ finally:
99
+ forget_app()
100
+ assert "[failed: the script says this call fails]" in capsys.readouterr().out
44
101
 
45
102
 
46
103
  def test_init_never_overwrites_without_force(tmp_path: Path) -> None: