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.
- chassis_harness-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +61 -0
- chassis_harness-0.1.0/.github/ISSUE_TEMPLATE/config.yml +11 -0
- chassis_harness-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +52 -0
- chassis_harness-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +19 -0
- chassis_harness-0.1.0/.github/workflows/ci.yml +80 -0
- chassis_harness-0.1.0/.github/workflows/docs.yml +61 -0
- chassis_harness-0.1.0/.github/workflows/release.yml +114 -0
- chassis_harness-0.1.0/.gitignore +33 -0
- chassis_harness-0.1.0/CHANGELOG.md +61 -0
- chassis_harness-0.1.0/CONTRIBUTING.md +88 -0
- chassis_harness-0.1.0/LICENSE +202 -0
- chassis_harness-0.1.0/PKG-INFO +208 -0
- chassis_harness-0.1.0/README.md +180 -0
- chassis_harness-0.1.0/SECURITY.md +57 -0
- chassis_harness-0.1.0/docs/README.md +44 -0
- chassis_harness-0.1.0/docs/configuration.md +117 -0
- chassis_harness-0.1.0/docs/design.md +73 -0
- chassis_harness-0.1.0/docs/getting-started.md +126 -0
- chassis_harness-0.1.0/docs/langgraph.md +180 -0
- chassis_harness-0.1.0/docs/lifecycle.md +187 -0
- chassis_harness-0.1.0/docs/observability.md +152 -0
- chassis_harness-0.1.0/docs/plugin-author-guide.md +227 -0
- chassis_harness-0.1.0/docs/recipes.md +192 -0
- chassis_harness-0.1.0/docs/replay.md +111 -0
- chassis_harness-0.1.0/docs/security.md +122 -0
- chassis_harness-0.1.0/docs/troubleshooting.md +209 -0
- chassis_harness-0.1.0/docs/why-generations.md +102 -0
- chassis_harness-0.1.0/examples/basic_agent.py +219 -0
- chassis_harness-0.1.0/examples/quickstart.py +100 -0
- chassis_harness-0.1.0/examples/reactive_cascade.py +117 -0
- chassis_harness-0.1.0/examples/safe_provider_replacement.py +149 -0
- chassis_harness-0.1.0/mkdocs.yml +64 -0
- chassis_harness-0.1.0/pyproject.toml +115 -0
- chassis_harness-0.1.0/src/chassis/__init__.py +146 -0
- chassis_harness-0.1.0/src/chassis/agents.py +469 -0
- chassis_harness-0.1.0/src/chassis/budget/__init__.py +15 -0
- chassis_harness-0.1.0/src/chassis/budget/governor.py +174 -0
- chassis_harness-0.1.0/src/chassis/budget/models.py +131 -0
- chassis_harness-0.1.0/src/chassis/capabilities/__init__.py +51 -0
- chassis_harness-0.1.0/src/chassis/capabilities/keys.py +211 -0
- chassis_harness-0.1.0/src/chassis/capabilities/registry.py +333 -0
- chassis_harness-0.1.0/src/chassis/capabilities/snapshot.py +191 -0
- chassis_harness-0.1.0/src/chassis/config/__init__.py +26 -0
- chassis_harness-0.1.0/src/chassis/config/loader.py +150 -0
- chassis_harness-0.1.0/src/chassis/config/models.py +115 -0
- chassis_harness-0.1.0/src/chassis/config/reconcile.py +181 -0
- chassis_harness-0.1.0/src/chassis/core/__init__.py +23 -0
- chassis_harness-0.1.0/src/chassis/core/collections.py +45 -0
- chassis_harness-0.1.0/src/chassis/core/errors.py +228 -0
- chassis_harness-0.1.0/src/chassis/core/generation.py +121 -0
- chassis_harness-0.1.0/src/chassis/core/generations.py +289 -0
- chassis_harness-0.1.0/src/chassis/core/scope.py +434 -0
- chassis_harness-0.1.0/src/chassis/diagnostics.py +199 -0
- chassis_harness-0.1.0/src/chassis/evaluation.py +127 -0
- chassis_harness-0.1.0/src/chassis/harness.py +1189 -0
- chassis_harness-0.1.0/src/chassis/hooks/__init__.py +27 -0
- chassis_harness-0.1.0/src/chassis/hooks/registry.py +274 -0
- chassis_harness-0.1.0/src/chassis/hooks/types.py +138 -0
- chassis_harness-0.1.0/src/chassis/langgraph/__init__.py +31 -0
- chassis_harness-0.1.0/src/chassis/langgraph/graphs.py +273 -0
- chassis_harness-0.1.0/src/chassis/langgraph/runtime.py +309 -0
- chassis_harness-0.1.0/src/chassis/langgraph/tools.py +141 -0
- chassis_harness-0.1.0/src/chassis/persistence/__init__.py +31 -0
- chassis_harness-0.1.0/src/chassis/persistence/hashing.py +171 -0
- chassis_harness-0.1.0/src/chassis/persistence/snapshots.py +167 -0
- chassis_harness-0.1.0/src/chassis/plugins/__init__.py +34 -0
- chassis_harness-0.1.0/src/chassis/plugins/base.py +330 -0
- chassis_harness-0.1.0/src/chassis/plugins/lifecycle.py +119 -0
- chassis_harness-0.1.0/src/chassis/plugins/manifest.py +129 -0
- chassis_harness-0.1.0/src/chassis/plugins/registry.py +352 -0
- chassis_harness-0.1.0/src/chassis/plugins/resolver.py +628 -0
- chassis_harness-0.1.0/src/chassis/policy/__init__.py +24 -0
- chassis_harness-0.1.0/src/chassis/policy/engine.py +139 -0
- chassis_harness-0.1.0/src/chassis/policy/permissions.py +86 -0
- chassis_harness-0.1.0/src/chassis/py.typed +0 -0
- chassis_harness-0.1.0/src/chassis/replay/__init__.py +29 -0
- chassis_harness-0.1.0/src/chassis/replay/model.py +143 -0
- chassis_harness-0.1.0/src/chassis/replay/models.py +99 -0
- chassis_harness-0.1.0/src/chassis/replay/session.py +202 -0
- chassis_harness-0.1.0/src/chassis/runtime.py +316 -0
- chassis_harness-0.1.0/src/chassis/secrets/__init__.py +22 -0
- chassis_harness-0.1.0/src/chassis/secrets/base.py +75 -0
- chassis_harness-0.1.0/src/chassis/secrets/env.py +151 -0
- chassis_harness-0.1.0/src/chassis/secrets/redaction.py +135 -0
- chassis_harness-0.1.0/src/chassis/tasks/__init__.py +7 -0
- chassis_harness-0.1.0/src/chassis/tasks/manager.py +51 -0
- chassis_harness-0.1.0/src/chassis/telemetry/__init__.py +20 -0
- chassis_harness-0.1.0/src/chassis/telemetry/base.py +146 -0
- chassis_harness-0.1.0/src/chassis/telemetry/langsmith.py +167 -0
- chassis_harness-0.1.0/src/chassis/telemetry/recording.py +100 -0
- chassis_harness-0.1.0/src/chassis/testing/__init__.py +21 -0
- chassis_harness-0.1.0/src/chassis/testing/fakes.py +190 -0
- chassis_harness-0.1.0/src/chassis/testing/harness.py +184 -0
- chassis_harness-0.1.0/src/chassis/tools/__init__.py +39 -0
- chassis_harness-0.1.0/src/chassis/tools/executor.py +625 -0
- chassis_harness-0.1.0/src/chassis/tools/metadata.py +57 -0
- chassis_harness-0.1.0/src/chassis/tools/registry.py +290 -0
- chassis_harness-0.1.0/tests/budget/test_budget.py +158 -0
- chassis_harness-0.1.0/tests/budget/test_run_budgets.py +161 -0
- chassis_harness-0.1.0/tests/capabilities/test_capabilities.py +236 -0
- chassis_harness-0.1.0/tests/capabilities/test_snapshot.py +144 -0
- chassis_harness-0.1.0/tests/concurrency/test_generation_concurrency.py +261 -0
- chassis_harness-0.1.0/tests/concurrency/test_shutdown_races.py +194 -0
- chassis_harness-0.1.0/tests/config/test_config.py +381 -0
- chassis_harness-0.1.0/tests/core/test_generation.py +287 -0
- chassis_harness-0.1.0/tests/core/test_scope.py +281 -0
- chassis_harness-0.1.0/tests/hooks/test_hook_boundaries.py +285 -0
- chassis_harness-0.1.0/tests/hooks/test_hooks.py +261 -0
- chassis_harness-0.1.0/tests/integration/test_examples.py +47 -0
- chassis_harness-0.1.0/tests/integration/test_generation_lifecycle.py +429 -0
- chassis_harness-0.1.0/tests/langgraph/test_langgraph.py +503 -0
- chassis_harness-0.1.0/tests/persistence/test_hashing.py +176 -0
- chassis_harness-0.1.0/tests/persistence/test_snapshots.py +254 -0
- chassis_harness-0.1.0/tests/plugins/support.py +60 -0
- chassis_harness-0.1.0/tests/plugins/test_lifecycle.py +462 -0
- chassis_harness-0.1.0/tests/plugins/test_manifest.py +80 -0
- chassis_harness-0.1.0/tests/plugins/test_resolver.py +155 -0
- chassis_harness-0.1.0/tests/policy/test_policy.py +87 -0
- chassis_harness-0.1.0/tests/replay/test_replay.py +363 -0
- chassis_harness-0.1.0/tests/secrets/test_secrets.py +116 -0
- chassis_harness-0.1.0/tests/telemetry/test_instrumentation.py +130 -0
- chassis_harness-0.1.0/tests/telemetry/test_langsmith.py +190 -0
- chassis_harness-0.1.0/tests/telemetry/test_telemetry.py +76 -0
- chassis_harness-0.1.0/tests/test_docs_links.py +44 -0
- chassis_harness-0.1.0/tests/test_evaluation.py +156 -0
- chassis_harness-0.1.0/tests/test_package.py +12 -0
- chassis_harness-0.1.0/tests/test_public_api.py +203 -0
- chassis_harness-0.1.0/tests/test_runtime.py +365 -0
- chassis_harness-0.1.0/tests/testing/test_testing.py +170 -0
- chassis_harness-0.1.0/tests/tools/test_executor.py +378 -0
- chassis_harness-0.1.0/tests/tools/test_registry.py +229 -0
- 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.
|