chassis-harness 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 (132) hide show
  1. chassis_harness-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +61 -0
  2. chassis_harness-0.1.0/.github/ISSUE_TEMPLATE/config.yml +11 -0
  3. chassis_harness-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +52 -0
  4. chassis_harness-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  5. chassis_harness-0.1.0/.github/workflows/ci.yml +80 -0
  6. chassis_harness-0.1.0/.github/workflows/docs.yml +61 -0
  7. chassis_harness-0.1.0/.github/workflows/release.yml +114 -0
  8. chassis_harness-0.1.0/.gitignore +33 -0
  9. chassis_harness-0.1.0/CHANGELOG.md +61 -0
  10. chassis_harness-0.1.0/CONTRIBUTING.md +88 -0
  11. chassis_harness-0.1.0/LICENSE +202 -0
  12. chassis_harness-0.1.0/PKG-INFO +208 -0
  13. chassis_harness-0.1.0/README.md +180 -0
  14. chassis_harness-0.1.0/SECURITY.md +57 -0
  15. chassis_harness-0.1.0/docs/README.md +44 -0
  16. chassis_harness-0.1.0/docs/configuration.md +117 -0
  17. chassis_harness-0.1.0/docs/design.md +73 -0
  18. chassis_harness-0.1.0/docs/getting-started.md +126 -0
  19. chassis_harness-0.1.0/docs/langgraph.md +180 -0
  20. chassis_harness-0.1.0/docs/lifecycle.md +187 -0
  21. chassis_harness-0.1.0/docs/observability.md +152 -0
  22. chassis_harness-0.1.0/docs/plugin-author-guide.md +227 -0
  23. chassis_harness-0.1.0/docs/recipes.md +192 -0
  24. chassis_harness-0.1.0/docs/replay.md +111 -0
  25. chassis_harness-0.1.0/docs/security.md +122 -0
  26. chassis_harness-0.1.0/docs/troubleshooting.md +209 -0
  27. chassis_harness-0.1.0/docs/why-generations.md +102 -0
  28. chassis_harness-0.1.0/examples/basic_agent.py +219 -0
  29. chassis_harness-0.1.0/examples/quickstart.py +100 -0
  30. chassis_harness-0.1.0/examples/reactive_cascade.py +117 -0
  31. chassis_harness-0.1.0/examples/safe_provider_replacement.py +149 -0
  32. chassis_harness-0.1.0/mkdocs.yml +64 -0
  33. chassis_harness-0.1.0/pyproject.toml +115 -0
  34. chassis_harness-0.1.0/src/chassis/__init__.py +146 -0
  35. chassis_harness-0.1.0/src/chassis/agents.py +469 -0
  36. chassis_harness-0.1.0/src/chassis/budget/__init__.py +15 -0
  37. chassis_harness-0.1.0/src/chassis/budget/governor.py +174 -0
  38. chassis_harness-0.1.0/src/chassis/budget/models.py +131 -0
  39. chassis_harness-0.1.0/src/chassis/capabilities/__init__.py +51 -0
  40. chassis_harness-0.1.0/src/chassis/capabilities/keys.py +211 -0
  41. chassis_harness-0.1.0/src/chassis/capabilities/registry.py +333 -0
  42. chassis_harness-0.1.0/src/chassis/capabilities/snapshot.py +191 -0
  43. chassis_harness-0.1.0/src/chassis/config/__init__.py +26 -0
  44. chassis_harness-0.1.0/src/chassis/config/loader.py +150 -0
  45. chassis_harness-0.1.0/src/chassis/config/models.py +115 -0
  46. chassis_harness-0.1.0/src/chassis/config/reconcile.py +181 -0
  47. chassis_harness-0.1.0/src/chassis/core/__init__.py +23 -0
  48. chassis_harness-0.1.0/src/chassis/core/collections.py +45 -0
  49. chassis_harness-0.1.0/src/chassis/core/errors.py +228 -0
  50. chassis_harness-0.1.0/src/chassis/core/generation.py +121 -0
  51. chassis_harness-0.1.0/src/chassis/core/generations.py +289 -0
  52. chassis_harness-0.1.0/src/chassis/core/scope.py +434 -0
  53. chassis_harness-0.1.0/src/chassis/diagnostics.py +199 -0
  54. chassis_harness-0.1.0/src/chassis/evaluation.py +127 -0
  55. chassis_harness-0.1.0/src/chassis/harness.py +1189 -0
  56. chassis_harness-0.1.0/src/chassis/hooks/__init__.py +27 -0
  57. chassis_harness-0.1.0/src/chassis/hooks/registry.py +274 -0
  58. chassis_harness-0.1.0/src/chassis/hooks/types.py +138 -0
  59. chassis_harness-0.1.0/src/chassis/langgraph/__init__.py +31 -0
  60. chassis_harness-0.1.0/src/chassis/langgraph/graphs.py +273 -0
  61. chassis_harness-0.1.0/src/chassis/langgraph/runtime.py +309 -0
  62. chassis_harness-0.1.0/src/chassis/langgraph/tools.py +141 -0
  63. chassis_harness-0.1.0/src/chassis/persistence/__init__.py +31 -0
  64. chassis_harness-0.1.0/src/chassis/persistence/hashing.py +171 -0
  65. chassis_harness-0.1.0/src/chassis/persistence/snapshots.py +167 -0
  66. chassis_harness-0.1.0/src/chassis/plugins/__init__.py +34 -0
  67. chassis_harness-0.1.0/src/chassis/plugins/base.py +330 -0
  68. chassis_harness-0.1.0/src/chassis/plugins/lifecycle.py +119 -0
  69. chassis_harness-0.1.0/src/chassis/plugins/manifest.py +129 -0
  70. chassis_harness-0.1.0/src/chassis/plugins/registry.py +352 -0
  71. chassis_harness-0.1.0/src/chassis/plugins/resolver.py +628 -0
  72. chassis_harness-0.1.0/src/chassis/policy/__init__.py +24 -0
  73. chassis_harness-0.1.0/src/chassis/policy/engine.py +139 -0
  74. chassis_harness-0.1.0/src/chassis/policy/permissions.py +86 -0
  75. chassis_harness-0.1.0/src/chassis/py.typed +0 -0
  76. chassis_harness-0.1.0/src/chassis/replay/__init__.py +29 -0
  77. chassis_harness-0.1.0/src/chassis/replay/model.py +143 -0
  78. chassis_harness-0.1.0/src/chassis/replay/models.py +99 -0
  79. chassis_harness-0.1.0/src/chassis/replay/session.py +202 -0
  80. chassis_harness-0.1.0/src/chassis/runtime.py +316 -0
  81. chassis_harness-0.1.0/src/chassis/secrets/__init__.py +22 -0
  82. chassis_harness-0.1.0/src/chassis/secrets/base.py +75 -0
  83. chassis_harness-0.1.0/src/chassis/secrets/env.py +151 -0
  84. chassis_harness-0.1.0/src/chassis/secrets/redaction.py +135 -0
  85. chassis_harness-0.1.0/src/chassis/tasks/__init__.py +7 -0
  86. chassis_harness-0.1.0/src/chassis/tasks/manager.py +51 -0
  87. chassis_harness-0.1.0/src/chassis/telemetry/__init__.py +20 -0
  88. chassis_harness-0.1.0/src/chassis/telemetry/base.py +146 -0
  89. chassis_harness-0.1.0/src/chassis/telemetry/langsmith.py +167 -0
  90. chassis_harness-0.1.0/src/chassis/telemetry/recording.py +100 -0
  91. chassis_harness-0.1.0/src/chassis/testing/__init__.py +21 -0
  92. chassis_harness-0.1.0/src/chassis/testing/fakes.py +190 -0
  93. chassis_harness-0.1.0/src/chassis/testing/harness.py +184 -0
  94. chassis_harness-0.1.0/src/chassis/tools/__init__.py +39 -0
  95. chassis_harness-0.1.0/src/chassis/tools/executor.py +625 -0
  96. chassis_harness-0.1.0/src/chassis/tools/metadata.py +57 -0
  97. chassis_harness-0.1.0/src/chassis/tools/registry.py +290 -0
  98. chassis_harness-0.1.0/tests/budget/test_budget.py +158 -0
  99. chassis_harness-0.1.0/tests/budget/test_run_budgets.py +161 -0
  100. chassis_harness-0.1.0/tests/capabilities/test_capabilities.py +236 -0
  101. chassis_harness-0.1.0/tests/capabilities/test_snapshot.py +144 -0
  102. chassis_harness-0.1.0/tests/concurrency/test_generation_concurrency.py +261 -0
  103. chassis_harness-0.1.0/tests/concurrency/test_shutdown_races.py +194 -0
  104. chassis_harness-0.1.0/tests/config/test_config.py +381 -0
  105. chassis_harness-0.1.0/tests/core/test_generation.py +287 -0
  106. chassis_harness-0.1.0/tests/core/test_scope.py +281 -0
  107. chassis_harness-0.1.0/tests/hooks/test_hook_boundaries.py +285 -0
  108. chassis_harness-0.1.0/tests/hooks/test_hooks.py +261 -0
  109. chassis_harness-0.1.0/tests/integration/test_examples.py +47 -0
  110. chassis_harness-0.1.0/tests/integration/test_generation_lifecycle.py +429 -0
  111. chassis_harness-0.1.0/tests/langgraph/test_langgraph.py +503 -0
  112. chassis_harness-0.1.0/tests/persistence/test_hashing.py +176 -0
  113. chassis_harness-0.1.0/tests/persistence/test_snapshots.py +254 -0
  114. chassis_harness-0.1.0/tests/plugins/support.py +60 -0
  115. chassis_harness-0.1.0/tests/plugins/test_lifecycle.py +462 -0
  116. chassis_harness-0.1.0/tests/plugins/test_manifest.py +80 -0
  117. chassis_harness-0.1.0/tests/plugins/test_resolver.py +155 -0
  118. chassis_harness-0.1.0/tests/policy/test_policy.py +87 -0
  119. chassis_harness-0.1.0/tests/replay/test_replay.py +363 -0
  120. chassis_harness-0.1.0/tests/secrets/test_secrets.py +116 -0
  121. chassis_harness-0.1.0/tests/telemetry/test_instrumentation.py +130 -0
  122. chassis_harness-0.1.0/tests/telemetry/test_langsmith.py +190 -0
  123. chassis_harness-0.1.0/tests/telemetry/test_telemetry.py +76 -0
  124. chassis_harness-0.1.0/tests/test_docs_links.py +44 -0
  125. chassis_harness-0.1.0/tests/test_evaluation.py +156 -0
  126. chassis_harness-0.1.0/tests/test_package.py +12 -0
  127. chassis_harness-0.1.0/tests/test_public_api.py +203 -0
  128. chassis_harness-0.1.0/tests/test_runtime.py +365 -0
  129. chassis_harness-0.1.0/tests/testing/test_testing.py +170 -0
  130. chassis_harness-0.1.0/tests/tools/test_executor.py +378 -0
  131. chassis_harness-0.1.0/tests/tools/test_registry.py +229 -0
  132. chassis_harness-0.1.0/uv.lock +1520 -0
@@ -0,0 +1,61 @@
1
+ name: Bug report
2
+ description: Something behaves differently from what the documentation claims
3
+ title: "[bug]: "
4
+ labels: ["bug"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Before opening this: `harness.diagnostics.status()`, `diagnostics.plugins()`, and
10
+ `diagnostics.explain("entry-id")` answer most "why is this not active" questions,
11
+ and [troubleshooting](https://github.com/andreolli-davide/chassis/blob/main/docs/troubleshooting.md)
12
+ lists the errors and their causes.
13
+
14
+ Security issue? Report it privately instead:
15
+ https://github.com/andreolli-davide/chassis/security/advisories/new
16
+ - type: checkboxes
17
+ id: checks
18
+ attributes:
19
+ label: Checks
20
+ options:
21
+ - label: I ran the same code against the latest release (`python -c "import chassis; print(chassis.__version__)"`)
22
+ required: true
23
+ - label: I searched the existing issues
24
+ required: true
25
+ - type: input
26
+ id: version
27
+ attributes:
28
+ label: Chassis version
29
+ placeholder: "0.1.0"
30
+ validations:
31
+ required: true
32
+ - type: input
33
+ id: python
34
+ attributes:
35
+ label: Python version and platform
36
+ placeholder: "3.12.10 on macOS 15.3 (arm64)"
37
+ validations:
38
+ required: true
39
+ - type: textarea
40
+ id: what
41
+ attributes:
42
+ label: What happened, and what you expected
43
+ description: Include the exact error type and its structured `context` if there was one.
44
+ validations:
45
+ required: true
46
+ - type: textarea
47
+ id: reproduction
48
+ attributes:
49
+ label: Minimal reproduction
50
+ description: A runnable script or the smallest test that shows it. `TestHarness` and the fakes in `chassis.testing` usually keep this short.
51
+ render: python
52
+ validations:
53
+ required: true
54
+ - type: textarea
55
+ id: diagnostics
56
+ attributes:
57
+ label: Diagnostics output, if relevant
58
+ description: "`harness.diagnostics.status()`, `diagnostics.explain(...)`, or `diagnostics.generations()`."
59
+ render: text
60
+ validations:
61
+ required: false
@@ -0,0 +1,11 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Question, or "how do I do X?"
4
+ url: https://github.com/andreolli-davide/chassis/discussions
5
+ about: Ask in Discussions. Bug reports and feature requests have their own forms.
6
+ - name: Documentation
7
+ url: https://github.com/andreolli-davide/chassis/blob/main/docs/README.md
8
+ about: Getting started, recipes, troubleshooting, and the design guarantees.
9
+ - name: Security issue
10
+ url: https://github.com/andreolli-davide/chassis/security/advisories/new
11
+ about: Report privately, never in a public issue.
@@ -0,0 +1,52 @@
1
+ name: Feature request
2
+ description: Suggest a capability, an integration, or a documentation gap
3
+ title: "[feature]: "
4
+ labels: ["enhancement"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Chassis deliberately does *not* do several things — graph execution, LangSmith
10
+ experiment management, sandboxing in-process plugins, deterministic replay of
11
+ external systems, Python hot reload. Please check
12
+ [design.md](https://github.com/andreolli-davide/chassis/blob/main/docs/design.md)
13
+ first: "deliberate absences" is a decision list, not a backlog.
14
+ - type: textarea
15
+ id: problem
16
+ attributes:
17
+ label: The problem you have
18
+ description: Describe the situation in your application, not the API you want.
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: proposal
23
+ attributes:
24
+ label: What you tried, and what you would expect instead
25
+ validations:
26
+ required: true
27
+ - type: dropdown
28
+ id: area
29
+ attributes:
30
+ label: Area
31
+ options:
32
+ - lifecycle / scopes / generations
33
+ - capabilities / plugin authoring
34
+ - tools / policy / approval
35
+ - hooks / tasks
36
+ - budgets
37
+ - secrets
38
+ - LangGraph integration
39
+ - telemetry / observability
40
+ - configuration / reconciliation
41
+ - replay / evaluation
42
+ - documentation / examples
43
+ - other
44
+ validations:
45
+ required: true
46
+ - type: textarea
47
+ id: alternatives
48
+ attributes:
49
+ label: What you are doing today instead
50
+ description: A workaround tells us how expensive the gap really is.
51
+ validations:
52
+ required: false
@@ -0,0 +1,19 @@
1
+ ## What this changes
2
+
3
+ <!-- One paragraph. What behaviour, invariant, or documentation is different after this? -->
4
+
5
+ ## Why
6
+
7
+ <!-- The problem it solves. If it fixes a bug, what was the failure mode? -->
8
+
9
+ ## Checklist
10
+
11
+ - [ ] `uv run ruff check .`, `uv run ruff format --check .`, `uv run pyright`, `uv run pytest` all pass
12
+ - [ ] the change and its tests are in the same commit, and a plausible bug would fail them
13
+ - [ ] every caller was migrated and what the change obsoletes is deleted (no shims, aliases, or dead options)
14
+ - [ ] `docs/` and `CHANGELOG.md` updated if a public API, invariant, or limitation changed
15
+ - [ ] no placeholder implementations, TODOs-as-functionality, or pinned-to-current-text tests
16
+
17
+ ## Notes for the reviewer
18
+
19
+ <!-- Trade-offs you decided, alternatives you rejected, and anything you are unsure about. -->
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ci-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ checks:
14
+ name: lint, type-check, test (py${{ matrix.python-version }})
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.12", "3.13"]
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v5
25
+ with:
26
+ enable-cache: true
27
+ cache-dependency-glob: "uv.lock"
28
+ python-version: ${{ matrix.python-version }}
29
+
30
+ - name: Sync environment from the lockfile
31
+ run: uv sync --locked --all-extras --dev
32
+
33
+ - name: Ruff lint
34
+ run: uv run ruff check .
35
+
36
+ - name: Ruff format check
37
+ run: uv run ruff format --check .
38
+
39
+ - name: Pyright
40
+ run: uv run pyright
41
+
42
+ - name: Pytest
43
+ run: uv run pytest
44
+
45
+ package:
46
+ name: build and install the wheel
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v5
53
+ with:
54
+ enable-cache: true
55
+ cache-dependency-glob: "uv.lock"
56
+
57
+ - name: Build
58
+ run: uv build
59
+
60
+ # The wheel must work on its own, in an environment that has never seen the
61
+ # source tree: `import chassis`, the version resolving from the distribution
62
+ # name, and a real LangGraph run.
63
+ - name: Install the wheel into a clean environment
64
+ run: |
65
+ uv venv .smoke
66
+ uv pip install --python .smoke/bin/python dist/*.whl
67
+
68
+ - name: Import the installed package
69
+ run: |
70
+ .smoke/bin/python - <<'PY'
71
+ import chassis
72
+ import chassis.langgraph
73
+ import chassis.testing
74
+
75
+ assert chassis.__version__ != "0.0.0", "version did not resolve from the distribution"
76
+ print("chassis", chassis.__version__)
77
+ PY
78
+
79
+ - name: Run the quickstart against the installed package
80
+ run: .smoke/bin/python examples/quickstart.py
@@ -0,0 +1,61 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ paths: ["docs/**", "mkdocs.yml", "README.md", ".github/workflows/docs.yml"]
7
+ pull_request:
8
+ paths: ["docs/**", "mkdocs.yml", "README.md", ".github/workflows/docs.yml"]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: pages
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ build:
20
+ name: build the site
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ with:
28
+ enable-cache: true
29
+ cache-dependency-glob: "uv.lock"
30
+
31
+ # Strict mode fails on a broken link or an unknown target, so the site cannot
32
+ # be published with dead ends in it. tests/test_docs_links.py covers the same
33
+ # ground against the source tree, where links out of docs/ are real files.
34
+ - name: Build (strict)
35
+ run: |
36
+ uv sync --locked --no-dev --group docs
37
+ uv run mkdocs build --strict
38
+
39
+ - name: Configure Pages
40
+ uses: actions/configure-pages@v5
41
+
42
+ - name: Upload the site
43
+ uses: actions/upload-pages-artifact@v3
44
+ with:
45
+ path: site
46
+
47
+ deploy:
48
+ name: deploy to GitHub Pages
49
+ needs: build
50
+ if: github.ref == 'refs/heads/main'
51
+ runs-on: ubuntu-latest
52
+ environment:
53
+ name: github-pages
54
+ url: ${{ steps.deployment.outputs.page_url }}
55
+ permissions:
56
+ pages: write
57
+ id-token: write
58
+ steps:
59
+ - name: Deploy
60
+ id: deployment
61
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,114 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ dry_run:
9
+ description: "Build and verify only, do not publish"
10
+ type: boolean
11
+ default: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ build:
18
+ name: build and verify the distribution
19
+ runs-on: ubuntu-latest
20
+ outputs:
21
+ version: ${{ steps.version.outputs.version }}
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ with:
28
+ enable-cache: true
29
+ cache-dependency-glob: "uv.lock"
30
+
31
+ - name: Sync environment from the lockfile
32
+ run: uv sync --locked --all-extras --dev
33
+
34
+ - name: Lint, type-check, and test
35
+ run: |
36
+ uv run ruff check .
37
+ uv run ruff format --check .
38
+ uv run pyright
39
+ uv run pytest
40
+
41
+ - name: Read the project version
42
+ id: version
43
+ run: |
44
+ version=$(uv run python -c "import pathlib, tomllib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
45
+ echo "version=$version" >> "$GITHUB_OUTPUT"
46
+ echo "project version: $version"
47
+
48
+ # Publishing is irreversible, so the tag, the project version, and the
49
+ # changelog must agree before anything is built.
50
+ - name: The tag must match the project version
51
+ if: startsWith(github.ref, 'refs/tags/v')
52
+ run: |
53
+ tag="${GITHUB_REF_NAME#v}"
54
+ if [ "$tag" != "${{ steps.version.outputs.version }}" ]; then
55
+ echo "tag ${GITHUB_REF_NAME} does not match pyproject version ${{ steps.version.outputs.version }}" >&2
56
+ exit 1
57
+ fi
58
+ echo "tag and project version agree: $tag"
59
+
60
+ - name: The changelog must document this version
61
+ run: |
62
+ key="## [${{ steps.version.outputs.version }}]"
63
+ if ! grep -qF "$key" CHANGELOG.md; then
64
+ echo "CHANGELOG.md has no '$key' section" >&2
65
+ exit 1
66
+ fi
67
+ echo "changelog documents ${{ steps.version.outputs.version }}"
68
+
69
+ - name: Build
70
+ run: uv build
71
+
72
+ # The artifact, not the source tree: install the wheel into an environment
73
+ # that has never seen this checkout and run a real agent with it.
74
+ - name: Verify the built wheel
75
+ run: |
76
+ uv venv .smoke
77
+ uv pip install --python .smoke/bin/python dist/*.whl
78
+ .smoke/bin/python - <<'PY'
79
+ import chassis
80
+
81
+ expected = "${{ steps.version.outputs.version }}"
82
+ assert chassis.__version__ == expected, f"{chassis.__version__} != {expected}"
83
+ print("installed chassis-harness", chassis.__version__)
84
+ PY
85
+ .smoke/bin/python examples/quickstart.py
86
+
87
+ - name: Upload the distribution
88
+ uses: actions/upload-artifact@v4
89
+ with:
90
+ name: dist
91
+ path: dist/
92
+
93
+ publish:
94
+ name: publish to PyPI
95
+ needs: build
96
+ if: startsWith(github.ref, 'refs/tags/v') && inputs.dry_run != true
97
+ runs-on: ubuntu-latest
98
+ environment:
99
+ name: pypi
100
+ url: https://pypi.org/p/chassis-harness
101
+ permissions:
102
+ contents: read
103
+ # Trusted publishing: PyPI verifies this workflow's identity, so no API
104
+ # token is stored anywhere.
105
+ id-token: write
106
+ steps:
107
+ - name: Download the distribution
108
+ uses: actions/download-artifact@v4
109
+ with:
110
+ name: dist
111
+ path: dist/
112
+
113
+ - name: Publish
114
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .smoke/
9
+ site/
10
+
11
+ # Environment
12
+ .venv/
13
+ venv/
14
+ .env
15
+ .env.*
16
+ !.env.example
17
+
18
+ # Tooling caches
19
+ .pytest_cache/
20
+ .ruff_cache/
21
+ .pyright/
22
+ .mypy_cache/
23
+ .coverage
24
+ .coverage.*
25
+ htmlcov/
26
+
27
+ # Editors / OS
28
+ .DS_Store
29
+ .idea/
30
+ .vscode/
31
+
32
+ # Local scratch
33
+ .local/
@@ -0,0 +1,61 @@
1
+ # Changelog
2
+
3
+ All notable changes to Chassis are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) with the pre-1.0 caveat
6
+ that a minor release may break the documented surface.
7
+
8
+ ## [0.1.0] - 2026-09-16
9
+
10
+ First release. The distribution is `chassis-harness`; the import package is
11
+ `chassis`; Python 3.12+.
12
+
13
+ ### Added
14
+
15
+ - Scoped ownership and reversible effects: every harness-managed effect belongs to
16
+ exactly one scope, and a failed plugin setup reverts everything that setup created.
17
+ - Versioned capability contracts, provider registry, and immutable capability
18
+ snapshots.
19
+ - Plugin manifests and author API: `provides`, `requires`, `optional`, permissions,
20
+ and a resource-oriented context (`ctx.tools`, `ctx.hooks`, `ctx.agents`,
21
+ `ctx.tasks`, `ctx.secrets`, `ctx.cleanup`).
22
+ - Reactive dependency resolution with deterministic ordering, pending diagnosis,
23
+ and cycle detection.
24
+ - Immutable runtime generations with atomic publication, leases, draining, and
25
+ reachability-gated physical disposal (logical unload is not destruction).
26
+ - Scope-owned tool registry and the execution boundary: policy, approval, budget,
27
+ deadline, hooks, tracing, error normalization, and replay.
28
+ - Scope-owned hook registry with deterministic ordering over every declared
29
+ boundary: tool execution, agent runs, plugin lifecycle, generation publication
30
+ and draining, and policy decisions.
31
+ - Hierarchical budget governor: wall clock, tool calls, and child runs enforced at
32
+ harness boundaries; model calls, tokens, and estimated cost declared and reported,
33
+ with an explicit escape hatch for code that owns the model call.
34
+ - Secret providers with mandatory redaction across logs, traces, snapshots,
35
+ diagnostics, replay records, and error strings.
36
+ - LangGraph integration behind a minimal `AgentRuntime` protocol: agent definitions,
37
+ build-time-keyed graph caching, checkpointing, `Store`, streaming, and
38
+ interrupt/resume, with the run's immutable snapshot propagated through LangGraph's
39
+ public runtime context.
40
+ - LangSmith telemetry and runtime snapshots: lifecycle spans and events, generation
41
+ metadata, canonical hashing, and redaction.
42
+ - Declarative configuration: YAML or JSON desired state, a plugin catalog, drift
43
+ reporting, and reconciliation through the same path as programmatic composition.
44
+ A configuration can be passed to `Harness(...)` and is applied on start.
45
+ - Bounded record/replay of tool and model boundaries, with snapshot, lifecycle, and
46
+ interrupt boundaries recorded as attribution.
47
+ - Diagnostics that explain state from authoritative data: plugins, capabilities,
48
+ dependencies, generations, tools, hooks, agents, owned effects, desired state, and
49
+ `explain()` for why a plugin is (in)active.
50
+ - `TestHarness` and deterministic fakes, plus four self-asserting examples and
51
+ concurrency tests for generation lifetime and shutdown races.
52
+
53
+ ### Known limitations
54
+
55
+ - In-process Python plugins are trusted code; the policy engine is not a sandbox.
56
+ - Replay does not virtualize clocks, randomness, networks, databases, or the
57
+ filesystem, and only tool and model boundaries are replayable.
58
+ - Configuration changes are reconciled as replacements, not in-place
59
+ reconfiguration.
60
+ - An abandoned `AgentRegistry.stream` generator holds its generation lease until it
61
+ is closed or collected.
@@ -0,0 +1,88 @@
1
+ # Contributing
2
+
3
+ Thanks for considering it. This project optimises for lifecycle correctness, resource
4
+ ownership, and honest documentation — in that order — and the rules below are what
5
+ keep those properties from eroding.
6
+
7
+ ## Set up
8
+
9
+ ```bash
10
+ uv sync
11
+ uv run pytest
12
+ ```
13
+
14
+ [uv](https://docs.astral.sh/uv/) is the canonical project manager; `pyproject.toml`
15
+ and `uv.lock` are the canonical dependency state. Please do not add a parallel
16
+ `requirements.txt` or a second tool for the same job.
17
+
18
+ ## The gates
19
+
20
+ Every pull request must leave these green. They are the same commands CI runs.
21
+
22
+ ```bash
23
+ uv run ruff check .
24
+ uv run ruff format --check .
25
+ uv run pyright # strict
26
+ uv run pytest
27
+ ```
28
+
29
+ Additional jobs run in CI and should be run if you touch what they cover:
30
+
31
+ ```bash
32
+ uv build # the wheel must build
33
+ uv run mkdocs build --strict # if you touched docs/
34
+ uvx --from actionlint-py actionlint .github/workflows/*.yml # if you touched CI
35
+ ```
36
+
37
+ ## What a change should look like
38
+
39
+ - **One coherent unit per commit**, conventional-commit style
40
+ (`feat(runtime): …`, `fix(lifecycle): …`, `docs: …`, `test: …`). A commit that mixes
41
+ a behavioural change with unrelated cleanup will be asked to split.
42
+ - **The change and its tests travel together** when the tests define the same unit.
43
+ - **No placeholder implementations.** A stub, a `TODO: implement`, or a fake fallback
44
+ presented as functionality is a defect, not a draft.
45
+ - **Clean cutovers.** Migrate every caller and delete what the change obsoletes:
46
+ aliases, shims, re-exports, dead options. Do not leave a second way to do the same
47
+ thing beside the new one.
48
+ - **Documentation is part of the change** when it introduces or alters a public API,
49
+ an invariant, or a limitation.
50
+
51
+ ## Tests worth writing
52
+
53
+ A test earns its place if a plausible bug would fail it. Prefer behaviour, boundaries,
54
+ invariants, transitions, and real errors over plumbing:
55
+
56
+ - good: "a leased generation's provider is not disposed while the lease is held";
57
+ - good: "a nested run cannot spend more than its parent has left";
58
+ - not useful: asserting that a function was called, that a field was copied, or that
59
+ a value is non-empty.
60
+
61
+ Concurrency claims need concurrency tests: real tasks and event barriers, not
62
+ sequential calls to an async API (`tests/concurrency/` is the model to follow).
63
+
64
+ Documentation has one test: every relative link in `README.md`, `CHANGELOG.md`, and
65
+ `docs/` must resolve (`tests/test_docs_links.py`).
66
+
67
+ ## Documentation rules
68
+
69
+ - `docs/` is the product documentation and is published with `mkdocs-material`
70
+ (`mkdocs.yml`). Cross-boundary links (examples, changelog) are absolute GitHub URLs,
71
+ because the site only serves `docs/`.
72
+ - State limitations as plainly as capabilities. Chassis does not claim to be a
73
+ sandbox, does not claim deterministic replay of external systems, and does not claim
74
+ a budget dimension the harness cannot enforce. Keep it that way.
75
+ - If a documented guarantee changes, update `docs/design.md` and the test that backs
76
+ it in the same commit.
77
+
78
+ ## Releasing
79
+
80
+ Maintainers only. Record the change in `CHANGELOG.md` under a `## [x.y.z]` heading, set
81
+ the same version in `pyproject.toml`, then tag `vX.Y.Z`. The release workflow refuses
82
+ to publish unless the tag, the project version, and the changelog agree.
83
+
84
+ ## Conduct
85
+
86
+ Be specific, be kind, assume good faith. Argue about mechanisms and evidence, not
87
+ about people. Reviews here will name files, lines, and consequences; expect that and
88
+ extend the same precision back.