glas-harness/workplans/GLAS-WP-0003-first-channel-extension.md
codex 02b29af9ca
All checks were successful
ci / validate (push) Successful in 1m16s
fix(workplans): declare type: workplan on records the hub already holds
These files carried no type field at all. Selection is by 'type: workplan', so
they were invisible to every projection while the hub held a record for each —
and a forge-derived reset read those correct records as no longer deriving and
queued them for retirement.

Only the type line is added.

Refs STATE-WP-0083-T05

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-26 20:56:02 +02:00

4.2 KiB

id type title status state_hub_workstream_id
GLAS-WP-0003 workplan First channel extension: formalize the CLI channel contract finished 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:

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.

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".

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).

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.

id: GLAS-WP-0003-T04
status: done
priority: low
state_hub_task_id: "0a06387f-c7ed-4de9-999d-a7ba50e33d50"