Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
848129c
fix(codex): let the Codex session exit on stdin EOF before killing it…
bandzalkin Oct 21, 2026
0b6c1e4
feat(omp): add native RPC client and wire types
bandzalkin Oct 21, 2026
81c3a14
fix: retain native process containment and prompt admission ownership
bandzalkin Oct 22, 2026
259d1eb
fix: reap discoverable native detached descendants
bandzalkin Oct 22, 2026
f696f09
fix: bound native event delivery and complete wire contracts
bandzalkin Oct 22, 2026
0f8e226
fix: serialize structured native answers in the transport
bandzalkin Oct 22, 2026
13ee3f3
fix: close native RPC review gaps in transport ownership and coverage
AlexanderZ-Band Oct 22, 2026
28ba70a
refactor: compare MCP startup events to OmpEventType
AlexanderZ-Band Oct 22, 2026
73073c6
refactor: share native RPC parse helpers and protocol constants
AlexanderZ-Band Oct 22, 2026
292c805
fix: respect platform EOF contracts in Codex cleanup tests
bandzalkin Oct 22, 2026
19e3b43
fix: treat graceful RPC EOF as teardown, not a crash
AlexanderZ-Band Oct 22, 2026
098b548
fix: keep failed native prompts from reaping the RPC process
AlexanderZ-Band Oct 22, 2026
2ad483c
fix: await Windows job member kernel exit
bandzalkin Oct 22, 2026
1687240
fix: keep native RPC close from racing stdout and extra-gated imports
AlexanderZ-Band Oct 22, 2026
76ff1f7
fix: preserve accepted-turn finalizers during shutdown retries
bandzalkin Oct 22, 2026
10565fc
test: synchronize shutdown retries and release observer cleanup
AlexanderZ-Band Oct 22, 2026
cc0097e
fix: integrate shared shutdown finalizer repair
bandzalkin Oct 22, 2026
1c0d3b0
fix: compose Codex graceful shutdown with shared process helpers
bandzalkin Oct 22, 2026
8e7d9fc
Merge commit '1687240d1e46eb7840134a49d6e07edf9cccdf0c' into pr-897
bandzalkin Oct 22, 2026
67bbbd8
test: budget the complete native RPC lifecycle workload
bandzalkin Oct 22, 2026
330e8ab
refactor: split native RPC into single-concern modules and intent-led…
AlexanderZ-Band Oct 22, 2026
9ac09f4
docs: make test conventions and review catch intent, seams, and branches
AlexanderZ-Band Oct 22, 2026
d867375
fix: apply native RPC ready limits before the next frame
AlexanderZ-Band Oct 22, 2026
d9609a0
test: cover complementary native RPC branches without private polls
AlexanderZ-Band Oct 22, 2026
e1adbd6
docs: record Windows job containment as closed unless close leaks
AlexanderZ-Band Oct 22, 2026
1abe091
fix: fail-close native RPC overflow, logs, and blocked-drain close
AlexanderZ-Band Oct 22, 2026
d621cb4
refactor: assert the backlog ERROR itself instead of a filtered tauto…
AlexanderZ-Band Oct 22, 2026
7243890
fix: preserve Windows ownership for injected native process creation
bandzalkin Oct 22, 2026
e893e4c
test: allow backlog overflow to retire before acknowledgement
bandzalkin Oct 22, 2026
cbba792
fix: recheck native process group identity before claiming custody
bandzalkin Oct 22, 2026
f73c2be
test: cover immediate ready limits and platform-neutral EOF custody
bandzalkin Oct 22, 2026
8756ce8
fix: bound native callback retirement and preserve response identity
bandzalkin Oct 22, 2026
959e410
chore: integrate merged shutdown ownership scenarios
bandzalkin Oct 22, 2026
0720167
fix: keep native RPC usable after a late prompt error
AlexanderZ-Band Oct 22, 2026
c4ae7d8
test: initialize callback retirement clock before native startup
bandzalkin Oct 22, 2026
a65aea2
fix: keep Windows native RPC tests on the owned job spawn
AlexanderZ-Band Oct 22, 2026
7b4857a
refactor: type native RPC command names and host-tool results
AlexanderZ-Band Oct 22, 2026
b39b41a
fix: wait for owned descendants on the gate event, not a clock
AlexanderZ-Band Oct 22, 2026
0b0620e
test: keep owned launch doubles in the shared RPC kit
bandzalkin Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions .claude/skills/band-code-review/angle-groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,34 @@ and consequences differ.
contents, secrets).

**D — Test Quality**
Test code gets every angle above, more strictly, plus:
- Apply `band-python-test-conventions` "Coverage and realistic flows" to the
changed behavior and relevant existing tests, even when no test file changed.
Map scenarios to meaningful assertions, identify gaps, and name the concrete
regression each proposed test would catch. Check complete complex flows at
a boundary that preserves their real interactions; passing tests or high
line coverage alone are not sufficient evidence.
- A skipped, disabled, or silently-ignored failing test is always a blocking
issue (`band-python-test-conventions`).
Test code belongs here alone. Read the whole `band-python-test-conventions`
skill, not only "Coverage is branches, not lines". Apply A–C more strictly
than on production code (SSoT, intent-named helpers, no mixed-concern files,
no magic wire strings). Each of the following is an `issue:`, not a nit:

- Coverage of complementary branches, not line counts. Map each changed
`match`/`if`/timeout/OS decision to the test that proves each reachable
outcome. Proving fail-closed does not prove the success side; a
`skipif(os.name)` is a missing branch on the skipped OS. Name the
regression each proposed test would catch. Passing tests or high line
coverage alone are not evidence.
- Assertions of plumbing or assumptions: occupancy arithmetic, private
fields, mock call counts, event-type tallies as a proxy, empty collections
the test does not own, or tautologies. The assertion must be a named
projection of the decision.
- A test function that branches on scenario/kind (`if scenario != ...`) or
re-types a wire literal the peer or product already names. Parametrize
`(scenario, expected)` so each row *is* the claim.
- The interaction under test is patched away (`unittest.mock.patch` of
private names, stdlib spawn, or timeout constants) instead of a public
seam or a real peer. `AsyncMock` of the unit under test is the same smell.
- Wall-clock sleep or waiting out a grace period used as proof.
`asyncio.timeout` is a hung-test guard only; a public timeout of `0` is
"check now", and it still needs the complementary "finished before wait"
sequence.
- A skipped, disabled, or silently-ignored failing test.
- A Hypothesis model or kit file that looks like a collected test (a `Test*`
class, or a missing role docstring, in a non-`test_*.py` module).

**E — Edge-Case Flow Analysis**
Owns failures that need a *sequence* of state transitions or interactions. A
Expand Down
4 changes: 3 additions & 1 deletion .claude/skills/band-here-is-a-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ Only CONFIRMED and PARTIALLY-RIGHT findings that **matter** proceed to fixing.
"Matters" means: user-visible breakage, data corruption, security exposure, a real
maintenance trap, or a violated project invariant or rule (`AGENTS.md` and the
guideline skills `band-python-conventions` and `band-python-test-conventions` — e.g. duplicated logic,
magic literals, narrating comments). Filter out:
magic literals, narrating comments, tests that assert plumbing or assumptions,
patch internals, wait out wall-clock grace, branch on scenario inside the test,
or treat line coverage as branch coverage). Filter out:

- Pedantry and taste conflicts with the project's established conventions
(the codebase's existing idiom wins over the reviewer's preference).
Expand Down
12 changes: 10 additions & 2 deletions .claude/skills/band-plan/plan-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,20 @@ Test rules:
- Test behavior that can actually break, not defaults or implementation
trivia (see the `band-python-test-conventions` skill). A test that would
still pass under a symptom-level patch doesn't cover the root cause.
Cover complementary branches of each owned decision, not line execution;
assert a named projection of the outcome, not internals or unclaimed
empties. Parametrize `(scenario, expected)` — do not `if` on scenario
inside the test.
- When a material scenario needs end-to-end coverage to prove the behavior,
reuse or extend the `tests/e2e/baseline` toolkit (see the
`band-write-e2e-test` skill) instead of building new plumbing. Otherwise use
the lowest boundary that preserves the behavior being tested.
- A missing helper, fixture, or local peer is its own step, before the step
that uses it — never an empty mock in place of a real boundary.
- A missing helper, fixture, constructor seam, or local peer is its own
step, before the step that uses it — never `unittest.mock.patch` of a
private name, stdlib spawn, or timeout constant, and never an empty mock
in place of a real boundary. Timeouts are proven with a public `0` ("check
now") plus the complementary "finished before wait" sequence, not by
sleeping the grace period.

Scope rules:

Expand Down
19 changes: 13 additions & 6 deletions .claude/skills/band-python-conventions/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: band-python-conventions
description: Band Python SDK conventions for Python source, comments, docstrings, and error handling. Read before editing Python in this repository.
description: Band Python SDK conventions for Python source, comments, docstrings, and error handling. Read before editing Python in this repository. Test files also follow band-python-test-conventions; this skill still owns SSoT, intent-named helpers, and public test seams.
paths:
- "**/*.py"
---
Expand Down Expand Up @@ -44,7 +44,12 @@ paths:
- Use `%s` placeholders in log messages for lazy evaluation
- Use Pydantic v2 for data models; use `model_dump()` not `dict()`
- Target Python 3.11+; use `list[str]` not `List[str]`, `str | None` not `Optional[str]`
- Use async/await everywhere in async codebases; use `AsyncMock` for testing async methods
- Use async/await everywhere in async codebases. `AsyncMock` is a test double
at an external boundary, not a stand-in for the unit whose interaction a
test claims to prove. Public constructor seams (a `create_process` factory,
an instance timeout of `0` meaning "check now") beat `unittest.mock.patch`
of private names or stdlib spawn. Tests follow
`band-python-test-conventions`.
- Catch `pydantic.ValidationError` separately from generic `Exception`
- Use `raise ValueError(...)` for missing required config, not `logger.error()` + `sys.exit()`
- Never put issue-tracker references in code — no Linear issue IDs (e.g. `INT-123`), Linear URLs, or ticket numbers in comments, docstrings, or strings. Explain the *why* in plain terms instead. (Branch names, commit messages, and PR descriptions may reference issues.)
Expand All @@ -62,10 +67,12 @@ paths:
- Prefer a single source of truth for a value or closed vocabulary consumed in more
than one place: give it one definition — a constant, a `StrEnum`, or a small helper
— that every site references, rather than re-typing the same magic literal in a
producer and the consumer that reads it (a typo then fails silently). Keep genuinely
distinct vocabularies separate, though — don't merge two sets that only happen to
share some values today (e.g. the ACP `ChunkType` a chunk carries vs. the platform
`message_type` an event is posted under).
producer and the consumer that reads it (a typo then fails silently). This includes
tests: a wire status, prompt text, or error message typed in a peer and again in an
assertion is two copies. Keep genuinely distinct vocabularies separate, though —
don't merge two sets that only happen to share some values today (e.g. the ACP
`ChunkType` a chunk carries vs. the platform `message_type` an event is posted
under).
- Before writing new logic, search the codebase for an existing helper that does
or almost does it; extending it beats a parallel implementation.
- Use idiomatic constructs (`match`, decorators, context managers, recursion)
Expand Down
200 changes: 147 additions & 53 deletions .claude/skills/band-python-test-conventions/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,172 @@
---
name: band-python-test-conventions
description: Band Python SDK test conventions and coverage assessment. Use when writing or reviewing tests, assessing whether changed behavior is adequately covered, or checking that tests validate meaningful outcomes and realistic complex flows.
description: Band Python SDK test conventions and coverage assessment. Use when writing or reviewing tests, assessing whether changed behavior is adequately covered, or checking that tests validate meaningful outcomes and realistic complex flows. Also use when a test asserts internals, patches private names, waits on wall-clock grace, branches on scenario inside the test, or reports line coverage instead of complementary branches.
paths:
- "tests/**/*.py"
- "src/**/*.py"
---

# Test Conventions

## Coverage and realistic flows
`band-python-conventions` still applies: single source of truth, intent-named
helpers, and no mixed-concern files. This skill is the extra bar for tests.

## Coverage is branches, not lines

Assess the relevant existing tests whenever behavior changes or coverage is
reviewed, even if the diff adds no tests. A green suite, a test count, or a
line-coverage percentage alone does not establish adequate coverage.

- Map user-visible scenarios and important invariants to the tests and
assertions that prove them. Identify missing or weak coverage, prioritizing
consequential behavior, changed branches, and regressions over a numeric target.
- For each test, name the plausible bug it would catch. Assert the observable
outcome and relevant side effects; a test that only checks that a mock was
called or that nothing raised may leave the real behavior unverified.
- Exercise complex flows as realistic sequences, not just isolated steps.
Where relevant, cover follow-up turns, failure and recovery, cancellation,
restart/rehydration, concurrent rooms or agents, permissions, and cleanup.
Select scenarios the actual feature can reach; do not add every case to
every change.
- Drive the real entry point and interacting components at the lowest level
that preserves the behavior under test. Use realistic peers or fakes at
external boundaries; do not mock away the interaction whose correctness
the test claims to prove. Use the baseline toolkit for needed live E2E
coverage, not as a mandatory layer for every test.
- Extend useful existing tests before duplicating them. Add or strengthen
tests for meaningful in-scope gaps; report remaining gaps and unverified
scenarios explicitly. Follow `AGENTS.md` for synchronization and validation
scope, without silently expanding the task or launching unrelated suites.

## Test implementation
Map each production decision the change owns — a `match`/`case`, an `if` that
chooses an outcome, an OS spawn path, a timeout pending-vs-done wait — onto
the tests that prove it. For every such decision:

1. Name the reachable outcomes (succeed, fail-closed, ignore, retry, OS-A vs
OS-B).
2. Name the test that proves each as an observable sequence, not as a line
that executed.
3. Treat complementary pairs as separate scenarios. Proving one side does not
prove the other: backlog live-at-limit vs overflow; a callback that
finishes before the wait vs one still running; settlement before the
matching result vs result before settlement; POSIX process-group vs
Windows job.
4. Report remaining unexamined outcomes explicitly. A Hypothesis model covers
generated sequences of its Step alphabet, not branches those steps never
take.

Prioritize consequential behavior, changed branches, and regressions over a
numeric target. Do not add every case to every change; select scenarios the
actual feature can reach.

For each test, name the plausible bug it would catch. Assert the observable
outcome and relevant side effects; a test that only checks that a mock was
called or that nothing raised may leave the real behavior unverified.

Exercise complex flows as realistic sequences, not just isolated steps.
Where relevant, cover follow-up turns, failure and recovery, cancellation,
restart/rehydration, concurrent rooms or agents, permissions, and cleanup.

Extend useful existing tests before duplicating them. Follow `AGENTS.md` for
synchronization and validation scope, without silently expanding the task or
launching unrelated suites.

## Assertions show intent, not plumbing or assumptions

Tests must be declarative and intent-revealing, not a transcript of the
implementation. Assert on a readable projection of the observable outcome —
the thing the test is actually about — never on raw internals or on a side
effect that merely implies the real answer.

- `assert reply.outline == ["tool_call (permission)", "message", ...]` over a
hand-rolled comprehension pulling `message_type` out of each event dict.
- `assert record.levelno == logging.DEBUG` over inferring a log level
indirectly from whether two separate capture windows came back empty.
- A named helper (`owned_rpc_process`, `outline`) over poking `_proc` or
counting `AGENT_END` as a proxy for "the turn finished twice".
- Overload of `EVENT_BACKLOG_LIMIT + 1` over occupancy arithmetic
(`limit - 2`) that restates the queue's internals.

If writing the assertion requires re-deriving *how* the code decided
something, the test is checking the wrong thing — assert the decision
itself.

Do not assert an assumption the test does not own. Drop empty-collection
checks on a mapping test that is about session keys, tautologies such as
Windows `(None, None)`, and "observer stayed empty" when the claim is
fail-closed-and-restarted. If the assertion would still pass after inverting
the behavior on an unclaimed axis, it is noise, not coverage.

Test what really matters — behavior that can break. Don't write tests that
restate definitions (asserting dataclass defaults equal themselves, echoing a
constant) or otherwise cannot fail for a real reason.

Decide what correct looks like before running the code, then assert that;
never bend an assertion to match whatever the code currently produces.

## One concern per test, no in-test branching

Each `test_*.py` holds exactly one `class Test...` named for the behavior it
covers — no module-level test functions and no helper classes beside it.
When a suite outgrows one file, make it a package: `conftest.py` for
fixtures, a plain helper module (`kit.py`, a Hypothesis `lifecycle.py`) for
shared doubles, and one test file per concern.

A helper module in that package is not a collected test. Its module
docstring names the role (kit, peer, lifecycle model). It does not define a
`Test*` class. The collected test that *plays* a Hypothesis model lives in
`test_lifecycle_model.py` (see `tests/platform/linkclose/test_lifecycle_model.py`).

One test function, one claim. Parametrize `(scenario, error)` so each row's
assertion *is* the claim. A long test that dispatches with `if scenario !=
"aborted"` or `if os.name == "nt": return` is several tests hiding in one
function; split them.

Wire statuses, prompt texts, and error messages belong to one named constant
or enum that the peer, the product, and the assertion all reference. A
hard-coded `"complete"` / `"Approve"` in the test is a second copy of a
closed vocabulary.

## Drive the real boundary; inject, don't patch

Drive the real entry point and interacting components at the lowest level
that preserves the behavior under test. Use realistic peers or fakes at
external boundaries; do not mock away the interaction whose correctness the
test claims to prove.

- A constructor seam (`create_process=`) or a public instance timeout of `0`
("check now / no grace") is the test hook. `unittest.mock.patch` of
`asyncio.create_subprocess_exec`, a private `_close_process`, or a
`_CALLBACK_EXIT_TIMEOUT_S` constant is not.
- `AsyncMock` is a double at an external boundary (a client the function
*takes*), not a stand-in for the unit under test. A receipt-table test may
mock the RPC client; it then does not count as coverage of the client's
prompt path.
- Use the baseline toolkit for needed live E2E coverage, not as a mandatory
layer for every test.

## Time is a hung-test guard, not proof

Ordering, concurrency, and timeout behavior is proven with explicit
synchronization and a controlled clock (`AGENTS.md` "Test Synchronization"):
events, gates, barriers, or observable state transitions. Never use elapsed
wall time, `asyncio.sleep` of a grace period, or "it finished within a
duration" as proof.

- `async with asyncio.timeout(N)` around a test is a hung-test guard. It
must not be the mechanism that establishes the outcome.
- To prove a timeout fail-closed path, set the public timeout to `0` and
assert the failure. Do not wait out the production grace period.
- `timeout = 0` proves "still pending at the wait". It does not prove the
complementary branch: work that finishes before the wait succeeds. Cover
that with a sequence that completes, not with a longer sleep.

Real subprocess and network readiness stay independent of a controlled
clock. Cleanup must account for resources created during failed or cancelled
startup and await their confirmed release.

## Tests run on Ubuntu and Windows CI

Test across OSes; don't skip one. Build host paths with `tmp_path` or
`tests.paths.host_absolute_path`, never a hard-coded `"/opt/..."` (no drive,
so relative on Windows); check absoluteness with
`band.workspaces.is_host_absolute`.

Where behavior differs per OS, write one parametrize table that runs
everywhere, each row naming the `os.name` values that accept it.
`skipif(os.name ...)` is only for a boundary that cannot run on that OS,
never for a path, separator, or "this assertion is awkward on Windows". A
skip is a missing branch on the skipped OS.

## Paths, bugs, skips

- In tests, never derive repository-anchored paths with per-file
`Path(__file__).parents[N]` arithmetic — import the anchors from
`tests/paths.py` (`REPO_ROOT`, `SRC_ROOT`, `EXAMPLES_ROOT`, `KIT_DIR`,
`ENV_TEST_FILE`). Only genuinely package-relative paths (a fixture file
next to its test) stay relative to their own `__file__`.
- Test what really matters — behavior that can break. Don't write tests that
restate definitions (asserting dataclass defaults equal themselves, echoing a
constant) or otherwise cannot fail for a real reason; they add maintenance
cost without protection.
- **Tests must be declarative and intent-revealing, not a transcript of the
implementation.** Assert on a readable projection of the observable outcome
— the thing the test is actually about — never on raw internals or on a
side effect that merely implies the real answer. Concretely:
- `assert reply.outline == ["tool_call (permission)", "message", ...]` over
a hand-rolled comprehension pulling `message_type` out of each event dict.
- `assert record.levelno == logging.DEBUG` over inferring a log level
indirectly from whether two separate capture windows came back empty.
If writing the assertion requires re-deriving *how* the code decided
something, the test is checking the wrong thing — assert the decision
itself.
- **Tests run on Ubuntu and Windows CI — test across OSes, don't skip one.**
Build host paths with `tmp_path` or `tests.paths.host_absolute_path`, never a
hard-coded `"/opt/..."` (no drive, so relative on Windows); check absoluteness
with `band.workspaces.is_host_absolute`. Where behavior differs per OS, write
one parametrize table that runs everywhere, each row naming the `os.name`
values that accept it. `skipif(os.name ...)` is only for a boundary that
cannot run on that OS, never for a path or separator assumption.
- Decide what correct looks like before running the code, then assert that;
never bend an assertion to match whatever the code currently produces.
- A skipped, disabled, or silently ignored failing test is always a blocking
issue.
- A fix for a confirmed bug comes with a test that fails before the fix and
passes after it.
- For a stateful lifecycle component, add a Hypothesis model-based test over
generated step sequences (see `tests/platform/linkclose/test_lifecycle_model.py`).
- Ordering, concurrency and timeout behavior is proven with explicit
synchronization and a controlled clock: `AGENTS.md` "Test Synchronization".
- File layout (one test class per file, packages for larger suites):
`AGENTS.md` "Testing Structure".
generated step sequences.
Loading
Loading