6.9 KiB
TECH.md — Client-Side Harness-Support: notify-user & finish-task
Problem
The server is adding POST /harness-support/notify-user and POST /harness-support/finish-task endpoints (see warp-server spec). Third-party harnesses invoke server APIs through the oz CLI, not directly. We need:
- Two new
oz harness-supportsubcommands that call these endpoints. - API client methods in the Warp client to make the HTTP calls.
- Claude Code plugin skills so the harness can invoke the commands at the right time.
Relevant Code
crates/warp_cli/src/harness_support.rs— CLI arg definitions (HarnessSupportCommandenum)app/src/ai/agent_sdk/harness_support.rs— command dispatch + async runnersapp/src/server/server_api/harness_support.rs—HarnessSupportClienttrait +ServerApiimplapp/src/ai/agent_sdk/telemetry.rs—CliTelemetryEventenumapp/src/ai/agent_sdk/mod.rs (1193-1202)— telemetry mapping for harness-support commands../claude-code-warp-internal/plugins/oz-harness-support/— existing plugin (skills, hooks)
Current State
oz harness-support has two subcommands: ping and report-artifact. Each follows the same pattern:
- CLI layer (
warp_cli): clapArgs/Subcommandstructs define the command shape. - Handler layer (
agent_sdk/harness_support.rs): match on the command, get theHarnessSupportClient, spawn an async task, print result / terminate. - API client layer (
server_api/harness_support.rs):HarnessSupportClienttrait method +ServerApiimpl callingself.post_public_api(path, body). - Telemetry: each command has a
CliTelemetryEventvariant.
The Claude Code plugin currently has three skills (oz-report-pr, oz-report-artifact, oz-report-plan) and a hook that auto-reports plans. Skills are either shell scripts calling $OZ_CLI harness-support ... or SKILL.md instructions.
Proposed Changes
1. CLI layer (crates/warp_cli/src/harness_support.rs)
Add two variants to HarnessSupportCommand:
NotifyUser(NotifyUserArgs),
FinishTask(FinishTaskArgs),
NotifyUserArgs: single required --message string.
FinishTaskArgs:
--status <success|failure>(requiredTaskStatusenum via clapValueEnum)--summary(required string)
TaskStatus is a #[derive(ValueEnum)] enum with variants Success and Failure, converted to bool at the CLI boundary before calling the API client.
PR links and branches are not passed by the CLI — the server derives them from artifacts already reported via report-artifact.
2. API client layer (app/src/server/server_api/harness_support.rs)
Add request types:
#[derive(serde::Serialize)]
struct NotifyUserRequest { message: String }
#[derive(serde::Serialize)]
struct FinishTaskRequest {
success: bool,
summary: String,
}
Add two methods to HarnessSupportClient trait:
async fn notify_user(&self, message: &str) -> Result<()>;
async fn finish_task(&self, success: bool, summary: &str) -> Result<()>;
ServerApi impl calls self.post_public_api("harness-support/notify-user", &body) and self.post_public_api("harness-support/finish-task", &body) respectively. Both return empty JSON {} — we deserialize to serde_json::Value and discard (or use a unit-like response struct).
3. Handler layer (app/src/ai/agent_sdk/harness_support.rs)
Add two match arms in run() dispatching to new notify_user() and finish_task() functions. Follow the existing report_artifact pattern:
- Get
HarnessSupportClientfromServerApiProvider - Spawn async call
- On success: print confirmation (pretty) or
{}(JSON), terminate - On error:
report_fatal_error
4. Telemetry (app/src/ai/agent_sdk/telemetry.rs)
Add variants:
HarnessSupportNotifyUser,
HarnessSupportFinishTask { success: bool },
Wire into command_to_telemetry_event in mod.rs and the TelemetryEventDesc impl.
5. Claude Code plugin (../claude-code-warp-internal/plugins/oz-harness-support/)
New skill: oz-notify-user
skills/oz-notify-user/SKILL.md — instructs Claude Code to call:
$OZ_CLI harness-support notify-user --message '<message>'
when it wants to send a progress update to the user (e.g., after completing a milestone).
New skill: oz-finish-task
skills/oz-finish-task/SKILL.md — instructs Claude Code to call a shell script:
$OZ_CLI harness-support finish-task --status <success|failure> --summary '<summary>'
No PR/branch args needed since the server derives them from reported artifacts.
Hook wiring: optionally add a PostToolUse hook on a task-completion tool (if Claude Code exposes one) to auto-invoke finish-task. If no such hook point exists, rely on the skill instruction to call it manually before exiting.
End-to-End Flow
sequenceDiagram
participant CC as Claude Code (harness)
participant OZ as oz CLI
participant SA as ServerApi (client)
participant WS as warp-server
Note over CC,WS: Progress update
CC->>OZ: oz harness-support notify-user --message "..."
OZ->>SA: HarnessSupportClient::notify_user("...")
SA->>WS: POST /harness-support/notify-user
WS->>WS: NotifyUser → Slack/Linear
WS-->>SA: 200 {}
SA-->>OZ: Ok(())
OZ-->>CC: exit 0
Note over CC,WS: Task complete
CC->>OZ: oz harness-support finish-task --status success --summary "..."
OZ->>SA: HarnessSupportClient::finish_task(true, "...")
SA->>WS: POST /harness-support/finish-task {success, summary}
WS->>WS: Lookup artifacts → ReportOutput → state transition + Slack/Linear
WS-->>SA: 200 {}
SA-->>OZ: Ok(())
OZ-->>CC: exit 0
Risks and Mitigations
- Server endpoints not deployed yet: the CLI will 404 until the server changes land. Gate behind
FeatureFlag::AgentHarness(already gating all harness-support commands) so this is fine — both land before flag is widely enabled. - finish-task called multiple times: server handles idempotency via state-transition guards. CLI will surface the server error message if the task is already in a terminal state.
- Plugin skill discoverability: Claude Code only sees skills if the plugin is installed. The harness setup already installs this plugin, so no new wiring needed.
Testing and Validation
- Unit tests in
harness_support_tests.rs: mockHarnessSupportClient, verifynotify_userandfinish_taskare called with correct args, test JSON vs pretty output. - CLI parsing tests: verify clap parsing for the new subcommands (missing required args,
--status successvs--status failure). - Manual validation: run against local server with
--features with_local_server, verify Slack/Linear messages appear. - Plugin skills: test shell scripts with mock
OZ_CLIto verify correct arg construction.
Follow-ups
- Hook-based auto-invocation of finish-task if Claude Code adds a task-completion hook point