--- id: GLAS-WP-0003 type: workplan title: "First channel extension: formalize the CLI channel contract" status: finished state_hub_workstream_id: "78da8b34-27dd-4807-b7c8-321328dd2db8" --- Scoped by `GLAS-WP-0002-T04`: of glas-harness's four charter pillars (unified API, profile catalog, extension platform, observability), "extension platform" is entirely unbuilt for channels — `glas_harness.cli` is a channel in substance (a way a caller invokes a harness session) but not in form (no `Channel` contract exists; it's just a CLI module). This workplan does **one thing**: formalize the CLI as the first channel extension, so the *contract* exists before a second channel (Slack, HTTP, whatever) is attempted — not build a second channel now. Same discipline as ADR-002/ADR-004: generalize from a real second example, not in anticipation of one. ## Task: Define the Channel contract A `Channel` mirrors `Rein`'s shape at a different layer: it turns an external invocation (CLI args, a Slack message, an HTTP request) into the `sandbox_profile`/`repo`/`title`/`description`/`rein` inputs `run_task_through_rein` needs, and turns its `dict[str, Any]` result back into whatever the invocation medium expects (stdout JSON for CLI, a Slack reply, an HTTP response body). Minimal ABC: ```python class Channel(ABC): @abstractmethod def parse_invocation(self, raw: Any) -> GatewayInvocation: ... @abstractmethod def render_result(self, result: dict[str, Any]) -> Any: ... ``` Document in `docs/channel-contract.md`, parallel to `docs/harness-contract.md`. **Done (2026-07-26).** `src/glas_harness/channels/base.py`: `GatewayInvocation` dataclass + `Channel` ABC as sketched above. Documented in `docs/channel-contract.md`. ```task id: GLAS-WP-0003-T01 status: done priority: medium state_hub_task_id: "f6a63483-2780-4b6c-8e0a-791e10d9c91a" ``` ## Task: Refactor glas_harness.cli into a CLIChannel `src/glas_harness/channels/cli_channel.py`: `CLIChannel(Channel)` implementing `parse_invocation` (argparse → `GatewayInvocation`) and `render_result` (→ `json.dumps(..., indent=2)`, matching today's output exactly — this is a refactor, not a behavior change). `cli.py` becomes a thin `main()` that constructs a `CLIChannel` and calls it; existing CLI usage and output are unchanged. **Done (2026-07-26).** `cli.py`'s `run` handler now constructs `CLIChannel()`, calls `parse_invocation(args)`, invokes `run_task_through_rein` with the resulting `GatewayInvocation` fields, and prints `channel.render_result(result)` — same shape as before. Also fixed a real bug found while live-testing this: `ReinAharness`'s default `cli_bin` was still `"agent-harness"`, stale from before the rename — now `"rein-aharness"`. ```task id: GLAS-WP-0003-T02 status: done priority: medium state_hub_task_id: "f7d26f3b-0df2-4db5-be7b-33065868087c" ``` ## Task: Tests + verify no behavior change Unit tests for `CLIChannel.parse_invocation`/`render_result` in isolation, plus re-running the existing live-proof commands (both reins) to confirm identical CLI output before/after the refactor. **Done (2026-07-26).** `tests/test_cli_channel.py`, 4 new tests (27/27 passing overall). Live-verified with a real run through `python3 -m glas_harness.cli run ...` against `rein-aharness`/real Claude Code: real commit landed, output shape unchanged, `executor-sandbox` itself untouched (as with every prior live proof — `ext.bwrap` only mutates its ephemeral copy). ```task id: GLAS-WP-0003-T03 status: done priority: medium state_hub_task_id: "5012c88c-0b9c-4d12-97a8-07a58b7b84c4" ``` ## Task: Document the pattern for a second channel A short section in `docs/channel-contract.md` sketching what a second channel (e.g. a Slack or HTTP channel) would need to implement, without building one — this workplan stops at "the contract exists and one real implementation proves it," matching the discipline applied throughout this session's other deferred-until-a-second-example decisions. **Done (2026-07-26).** "Adding a second channel" section in `docs/channel-contract.md`, sketching a Slack channel without building one. ```task id: GLAS-WP-0003-T04 status: done priority: low state_hub_task_id: "0a06387f-c7ed-4de9-999d-a7ba50e33d50" ```