Skip to content

Add harness runtime plans#987

Merged
willwashburn merged 24 commits into
mainfrom
codex/harness-runtime-plans
May 26, 2026
Merged

Add harness runtime plans#987
willwashburn merged 24 commits into
mainfrom
codex/harness-runtime-plans

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented May 25, 2026

Summary

  • document the harness runtime plan and add the harnesses docs page
  • add TypeScript SDK harness plan types, static/dynamic resolution, spawn serialization, and session/pid result metadata
  • teach the broker to execute resolved PTY plans and add an OpenCode app-server worker path
  • address review feedback for legacy app-server plan compatibility, hook-time transport resolution, app-server request timeouts, auth env isolation, and agent metadata updates

Test Plan

  • Tests added or updated
  • Manual validation completed

Validation

  • cargo check -p agent-relay-broker
  • cargo test -p agent-relay-broker harness
  • cargo test -p agent-relay-broker app_server
  • npm --prefix packages/sdk run check
  • ../../node_modules/.bin/vitest run --config vitest.config.ts src/tests/harness.test.ts src/tests/lifecycle-hooks.test.ts (from packages/sdk)
  • git diff --check
  • npm --prefix web run build (from prior docs update commit)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds harness config/schema support and SDK resolver wiring so the SDK can resolve named harnesses (static or attached) into broker-executable PTY or headless app-server configs; broker and worker code accept and enforce harnessConfig, track sessionId/pid, and add a headless app-server worker driver, tests, docs, and trajectories.

Changes

Harness Runtime Config Implementation

Layer / File(s) Summary
Project trajectories & metadata
.trajectories/completed/2026-05/*, .trajectories/index.json
New and updated trajectory JSON/MD/trace records and index entries documenting harness decisions, terminology, and checklist items.
Wire Protocol Harness Config Types
crates/broker/src/protocol.rs, tests
Adds PTY/headless harness types and ResolvedHarnessConfig (custom deserializer, legacy alias support), boxes large enum payloads, and extends AgentSpec/BrokerEvent with session_id/harness_config and tests for serde behavior.
HTTP Spawn Endpoint Harness Handling
crates/broker/src/listen_api.rs, tests
/api/spawn accepts optional harnessConfig/aliases, deserializes into ResolvedHarnessConfig, validates, forwards it in ListenApiRequest::Spawn, and updates tests/fixtures.
Spawn Spec Resolution
crates/broker/src/runtime/spawn_spec.rs
build_http_api_spawn_spec gains harness_config parameter, reconciles transport vs harness runtime (error on mismatch), derives session_id from harness config, and populates AgentSpec.harness_config/session_id.
App-Server CLI Driver & Headless Worker
crates/broker/src/cli/mod.rs, crates/broker/src/runtime/app_server.rs, crates/broker/src/runtime/mod.rs
Adds AppServerCommand, telemetry/log tag wiring, and run_app_server_worker implementing newline-delimited JSON envelope IO, init/deliver/ping/shutdown handling, opencode prompt injection, auth parsing, release behavior, and unit tests.
PTY Child PID Tracking & Worker Events
crates/broker/src/pty.rs, crates/broker/src/pty_worker.rs
Adds PtySession::child_pid() accessor; try_emit_worker_ready includes pid in worker_ready frames and all call sites pass pty.child_pid().
Worker Spawn Harness Integration
crates/broker/src/worker.rs, tests
WorkerRegistry::spawn supports spec.harness_config: PTY path fills command/args/cwd/env/session from harness; Headless path validates app-server config, builds app-server spawn command, injects/suppresses auth env, captures harness host pid, reorders env wiring, and computes per-spec release grace; helpers and tests added.
Broker Runtime Event Handling
crates/broker/src/runtime/api.rs, crates/broker/src/runtime/relaycast_events.rs, crates/broker/src/runtime/worker_events.rs, crates/broker/src/supervisor.rs, tests
API spawn threads harness_config into spawn spec; agent_spawned and HTTP responses include sessionId; worker_ready events include sessionId/pid and update worker handle harness_pid; relaycast WS spawn includes session_id/harness_config; PTY input/resize endpoints validate runtime.
SDK Harness Resolution Module
packages/sdk/src/harness.ts
New harness module: types for PTY/headless, resolveStaticHarnessConfig with template rendering ({args}, {modelArgs}, {task}), env merging, cwd selection, command resolution/searchPaths, harnessLookupKeys, and helpers.
SDK Client Spawn Methods
packages/sdk/src/client.ts
Spawn methods return Promise<SpawnAgentResult> (name/runtime plus optional sessionId/pid). resolveSpawnTransport prioritizes input.transport → harnessConfig.runtime → provider defaults; spawn payloads include harnessConfig; headless-provider validation relaxed when harnessConfig provided.
SDK Relay Harness Integration
packages/sdk/src/relay.ts
AgentRelay accepts harnesses registry and broker.lifetime boundary, registerHarness() API, resolves harnessConfig (static or attached resolver when attached), passes harnessConfig into spawn calls, and threads sessionId/pid into Agent handles via ensure/update helpers.
SDK Types & Protocol Updates
packages/sdk/src/types.ts, packages/sdk/src/protocol.ts, packages/sdk/src/lifecycle-hooks.ts, packages/sdk/src/index.ts
Adds SpawnAgentResult type, adds harnessConfig to spawn inputs, extends AgentSpec with session_id/harness_config, enriches BrokerEvent/worker_ready payloads with sessionId/pid, updates lifecycle hook result typing, and re-exports harness module at SDK root.
Testing, Documentation & Changelog
packages/sdk/src/__tests__/*, CHANGELOG.md, docs/harness-runtime-config.md, web/content/docs/harnesses.mdx, web/lib/docs-nav.ts
Adds Vitest suites for harness resolution and lifecycle hook behavior; comprehensive architecture and user docs for harness adapters; changelog entries and site nav for Harnesses.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested reviewers

  • khaliqgant

Poem

🐰 I stitched a harness from templates and care,

Broker hums steady, headless sessions in air.
PTYs whisper PIDs, app-servers take flight,
SDK finds plans, spawns with session delight.
Hooray—configs bloom, and metadata's bright!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/harness-runtime-plans

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 28 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread packages/sdk/src/client.ts Outdated
Comment thread crates/broker/src/runtime/app_server.rs Outdated
Comment thread packages/sdk/src/harness.ts Outdated
Comment thread docs/harness-runtime-plan.md Outdated
Comment thread docs/harness-runtime-plan.md Outdated
Comment thread docs/harness-runtime-plan.md Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Preview deployed!

Environment URL
Web https://d3osrf6yi644pb.cloudfront.net

This preview will be cleaned up when the PR is merged or closed.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84d87355ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/broker/src/runtime/app_server.rs Outdated
let session_id = cmd.session_id.clone();
let release = cmd.release.trim().to_ascii_lowercase();
let auth = app_server_auth_from_env();
let http = reqwest::Client::new();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Set timeout on app-server HTTP client

Create the app-server reqwest::Client with an explicit timeout; Client::new() uses no request timeout, so a slow or partitioned endpoint can block send_opencode_prompt/release calls indefinitely. In that state the worker stops progressing deliveries (and may never process shutdown cleanly), which can stall message flow until an external kill happens.

Useful? React with 👍 / 👎.

Comment thread packages/sdk/src/harness.ts Outdated
Comment on lines +211 to +214
for (const searchPath of searchPaths) {
const candidate = path.join(expandHome(searchPath), command);
if (existsSync(candidate)) {
return candidate;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep harness command resolution broker-side

Do not rewrite command to a local absolute path during SDK-side static harness resolution. existsSync against searchPaths runs on the caller machine, so in detached/remote broker setups this can serialize a host-specific path that does not exist where the broker executes, causing spawn failures even when the plain command name would have worked in the broker environment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 17 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/broker/src/protocol.rs Outdated
Comment thread packages/sdk/src/client.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sdk/src/client.ts (1)

615-635: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recompute transport and headless validation after spawn patches.

Line 616/Line 617 resolve and validate using the original input, but runBeforeSpawn can patch harnessPlan. That can produce stale transport in the POST body and reject valid patched requests.

Suggested fix
 async spawnProvider(input: SpawnProviderInput): Promise<SpawnAgentResult> {
-  const transport = resolveSpawnTransport(input);
-  if (transport === 'headless' && !isHeadlessProvider(input.provider) && !input.harnessPlan) {
-    throw new Error(
-      `provider '${input.provider}' does not support headless transport (supported: claude, opencode)`
-    );
-  }
-
   const beforeCtx: BeforeAgentSpawnContext = {
     kind: 'provider',
     input,
@@
   };
   const t0 = Date.now();
   const resolvedInput = (await this.runBeforeSpawn(beforeCtx)) as SpawnProviderInput;
+  const transport = resolveSpawnTransport(resolvedInput);
+  if (transport === 'headless' && !isHeadlessProvider(resolvedInput.provider) && !resolvedInput.harnessPlan) {
+    throw new Error(
+      `provider '${resolvedInput.provider}' does not support headless transport (supported: claude, opencode)`
+    );
+  }
   try {
     const result = await this.transport.request<SpawnAgentResult>('/api/spawn', {
       method: 'POST',
       body: JSON.stringify(buildSpawnProviderBody(resolvedInput, transport)),
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/client.ts` around lines 615 - 635, The current spawnProvider
computes transport and validates headless support from the original input before
running runBeforeSpawn, so patches (e.g., to harnessPlan) can make the transport
or validation stale; update spawnProvider to call resolveSpawnTransport and
perform the headless isHeadlessProvider check again after obtaining
resolvedInput (the result of runBeforeSpawn) and use the recomputed transport
when calling buildSpawnProviderBody and when performing the headless-rejection
throw; specifically, reassign or create a new transport variable from
resolveSpawnTransport(resolvedInput) and re-run the headless conditional before
the POST to /api/spawn.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.trajectories/index.json:
- Line 10: Update the "path" entries in .trajectories/index.json to use
canonical repo-relative paths instead of local absolute paths: replace values
like
"/private/tmp/relay-harness-runtime-plans/.trajectories/completed/2026-04/traj_05xg7j388bc4.json"
with ".trajectories/completed/2026-04/traj_05xg7j388bc4.json" (apply the same
normalization for all occurrences referenced, e.g., entries at lines 17, 24,
906, 913, 1123), ensure the "path" property uses a consistent schema across all
records, and run a quick schema/lookup check to confirm consumers resolve the
repo-relative paths correctly.

In `@crates/broker/src/runtime/app_server.rs`:
- Around line 16-17: The HTTP client calls in run_app_server_worker currently
use reqwest::Client::new() and call request.send().await in
send_app_server_request without an explicit per-call deadline; update the code
to enforce a strict timeout so broker-critical paths (deliver_relay,
shutdown_worker) do not block for the default client timeout. Either construct
the client with a bounded timeout via
reqwest::Client::builder().timeout(Duration::from_secs(...)) or wrap the
per-request future in tokio::time::timeout(...) around the request.send().await
inside send_app_server_request (and propagate/handle the timeout error up to
deliver_relay/shutdown_worker), referencing run_app_server_worker,
send_app_server_request, deliver_relay, shutdown_worker and the
reqwest::Client::new()/request.send().await sites to locate where to apply the
change.

In `@crates/broker/src/worker.rs`:
- Around line 325-341: Before applying plan.auth into harness_env, explicitly
remove any existing AGENT_RELAY_APP_SERVER_AUTH_* entries so stale credentials
from self.worker_env are not inherited; modify the code around the harness_env
population (the block handling plan.auth in the function where harness_env is
built) to first remove keys "AGENT_RELAY_APP_SERVER_AUTH_TYPE",
"AGENT_RELAY_APP_SERVER_AUTH_TOKEN", "AGENT_RELAY_APP_SERVER_AUTH_USERNAME", and
"AGENT_RELAY_APP_SERVER_AUTH_PASSWORD" from harness_env (or clear them from the
base env copy) and then apply the plan.auth pushes; apply the same change in the
analogous spot handling the other plan auth block (the one corresponding to
lines 514-519).

In `@packages/sdk/src/relay.ts`:
- Around line 1479-1491: ensureAgentHandle currently returns an existing Agent
from knownAgents without merging new metadata, so later-arriving sessionId/pid
are ignored; update ensureAgentHandle to, when an existing agent is found, merge
incoming metadata into that Agent (e.g., set or update sessionId and pid on the
Agent instance or call an Agent method like setMetadata/updateSession) before
returning it, ensuring makeAgent remains the creation path and knownAgents still
caches the instance; apply the same merge/update logic to the other place that
returns existing agent handles without absorbing metadata (the other
ensure/lookup path that uses makeAgent/knownAgents).

In `@web/content/docs/harnesses.mdx`:
- Around line 132-133: Replace the incorrect SDK method reference snapshotPty
with the actual client method snapshot(...) in the docs line that lists PTY-only
routes (alongside sendInput and resizePty) so readers can copy/paste the correct
API call; update the text to mention snapshot(...) as the PTY snapshot method to
match the client API.

---

Outside diff comments:
In `@packages/sdk/src/client.ts`:
- Around line 615-635: The current spawnProvider computes transport and
validates headless support from the original input before running
runBeforeSpawn, so patches (e.g., to harnessPlan) can make the transport or
validation stale; update spawnProvider to call resolveSpawnTransport and perform
the headless isHeadlessProvider check again after obtaining resolvedInput (the
result of runBeforeSpawn) and use the recomputed transport when calling
buildSpawnProviderBody and when performing the headless-rejection throw;
specifically, reassign or create a new transport variable from
resolveSpawnTransport(resolvedInput) and re-run the headless conditional before
the POST to /api/spawn.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f6556c6-037c-4f59-962c-1ebc59e3cc9e

📥 Commits

Reviewing files that changed from the base of the PR and between ff1d4da and 298ec21.

📒 Files selected for processing (30)
  • .trajectories/completed/2026-05/traj_7i9tigaejfje.json
  • .trajectories/completed/2026-05/traj_7i9tigaejfje.md
  • .trajectories/completed/2026-05/traj_i2pjnx3dll5b.json
  • .trajectories/completed/2026-05/traj_i2pjnx3dll5b.md
  • .trajectories/completed/2026-05/traj_i2pjnx3dll5b.trace.json
  • .trajectories/index.json
  • CHANGELOG.md
  • crates/broker/src/cli/mod.rs
  • crates/broker/src/listen_api.rs
  • crates/broker/src/protocol.rs
  • crates/broker/src/runtime/api.rs
  • crates/broker/src/runtime/app_server.rs
  • crates/broker/src/runtime/mod.rs
  • crates/broker/src/runtime/relaycast_events.rs
  • crates/broker/src/runtime/spawn_spec.rs
  • crates/broker/src/runtime/tests.rs
  • crates/broker/src/runtime/worker_events.rs
  • crates/broker/src/supervisor.rs
  • crates/broker/src/worker.rs
  • docs/harness-runtime-plan.md
  • packages/sdk/src/__tests__/harness.test.ts
  • packages/sdk/src/client.ts
  • packages/sdk/src/harness.ts
  • packages/sdk/src/index.ts
  • packages/sdk/src/lifecycle-hooks.ts
  • packages/sdk/src/protocol.ts
  • packages/sdk/src/relay.ts
  • packages/sdk/src/types.ts
  • web/content/docs/harnesses.mdx
  • web/lib/docs-nav.ts

Comment thread .trajectories/index.json Outdated
Comment thread crates/broker/src/runtime/app_server.rs Outdated
Comment thread crates/broker/src/worker.rs Outdated
Comment thread packages/sdk/src/relay.ts
Comment thread web/content/docs/harnesses.mdx Outdated
@willwashburn
Copy link
Copy Markdown
Member Author

Addressed the valid review findings in commit 29c9e3a.

Fixed:

  • Accepted legacy harness plans with runtime: app_server by normalizing them to the canonical headless app_server driver on broker deserialize.
  • Recomputed spawnProvider transport and headless validation after beforeAgentSpawn patches, so hook-added harnessPlan values affect the POST body and validation.
  • Added a bounded timeout to app-server HTTP requests.
  • Prevented inherited AGENT_RELAY_APP_SERVER_AUTH_* values from leaking into app-server harness workers, while still applying explicit plan auth.
  • Updated existing SDK Agent handles with later sessionId, pid, runtime, and channels metadata instead of returning stale handles.
  • Expanded ~/... direct harness command paths.
  • Corrected the harness runtime plan docs for optional delivery.mode, optional host.ownership, and the SDK event hook API.
  • Normalized trajectory index paths to repo-relative paths.

Checked and did not change:

  • The snapshotPty docs comment is stale against the current harnesses page; it no longer references snapshotPty.
  • The latest CodeRabbit run had no actionable comments. The remaining MDX parser warning appears tool-specific; the web build passed after the docs rewrite.

Validation:

  • cargo check -p agent-relay-broker
  • cargo test -p agent-relay-broker harness
  • cargo test -p agent-relay-broker app_server
  • npm --prefix packages/sdk run check
  • ../../node_modules/.bin/vitest run --config vitest.config.ts src/tests/harness.test.ts src/tests/lifecycle-hooks.test.ts
  • git diff --check

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sdk/src/relay.ts (1)

1488-1497: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't overwrite an existing handle's runtime with the default 'pty'.

ensureAgentHandle() now always updates existing handles, but its runtime parameter still defaults to 'pty'. Call sites that omit it currently rewrite a headless handle to PTY on the relay_inbound path. Preserve the current runtime when none was supplied.

Possible fix
   private ensureAgentHandle(
     name: string,
-    runtime: AgentRuntime = 'pty',
+    runtime: AgentRuntime | undefined = undefined,
     channels: string[] = [],
     metadata?: { sessionId?: string; pid?: number }
   ): Agent {
     const existing = this.knownAgents.get(name);
     if (existing) {
       this.updateAgentHandle(existing, runtime, channels, metadata);
       return existing;
     }
-    const agent = this.makeAgent(name, runtime, channels, metadata);
+    const agent = this.makeAgent(name, runtime ?? 'pty', channels, metadata);
     this.knownAgents.set(name, agent);
     return agent;
   }

   private updateAgentHandle(
     agent: Agent,
-    runtime: AgentRuntime,
+    runtime: AgentRuntime | undefined,
     channels: string[],
     metadata?: { sessionId?: string; pid?: number }
   ): void {
     const internal = agent as InternalAgent;
-    internal._setRuntime(runtime);
+    if (runtime !== undefined) {
+      internal._setRuntime(runtime);
+    }
     if (channels.length > 0) {
       internal._setChannels([...new Set([...agent.channels, ...channels])]);
     }
     internal._setMetadata(metadata);
   }

Also applies to: 1504-1515

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/relay.ts` around lines 1488 - 1497, The ensureAgentHandle
function currently defaults runtime to 'pty' which causes existing agent handles
to be overwritten when callers omit runtime; change the signature so runtime is
optional (no default) and modify the logic so updateAgentHandle(existing, ...)
only applies a new runtime when the caller passed one (i.e. check for runtime
!== undefined), while when creating a new Agent use runtime ?? 'pty' to fall
back to PTY; update both ensureAgentHandle and the corresponding update path
(and the other similar block around lines 1504-1515) to preserve existing
runtime unless an explicit runtime was provided.
🧹 Nitpick comments (1)
packages/sdk/src/__tests__/lifecycle-hooks.test.ts (1)

218-233: ⚡ Quick win

Drop the explicit transport from this regression test.

Because the input already sets transport: 'headless', the request body stays headless even if spawnProvider() stops recomputing transport after beforeAgentSpawn. Omitting transport here would actually lock in the hook-added harnessPlan as the thing that flips the request to headless.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/__tests__/lifecycle-hooks.test.ts` around lines 218 - 233,
The test is forcing transport to 'headless' which masks the behavior the hook
should set; remove the explicit transport property from the spawnProvider call
(the call to spawnProvider with name: 'patched-headless' / provider:
'custom-provider') and also remove the transport assertion from the
expect(captures[0].body) object so the test verifies the hook-added harnessPlan
(sessionId: 'ses_hook', runtime: 'headless', driver: 'app_server') and that
spawnProvider/beforeAgentSpawn logic flips transport indirectly via harnessPlan
rather than being pre-set by the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/sdk/src/harness.ts`:
- Around line 210-223: resolveCommand is returning early for Windows-style paths
like "~\bin\claude" because expandHome currently only expands "~/" and leaves
"~\" intact, so expandedCommand.includes('\\') triggers the direct-path return
before home expansion; fix by updating expandHome to recognize and replace a
leading "~\" as well as "~/" (handling both path separators) so that
resolveCommand's use of expandHome yields a fully expanded path and the
includes('/')/includes('\\') checks operate on the expanded value.

In `@packages/sdk/src/relay.ts`:
- Around line 1195-1198: listAgents() is treating the broker snapshot as a merge
target instead of authoritative state, so updateAgentHandle(existing,
entry.runtime, entry.channels, entry) must stop unioning channels and instead
sync them to the snapshot; change updateAgentHandle (or add a new
syncAgentHandle) to accept a "replaceChannels" behavior and assign
existing.channels = entry.channels (including empty arrays to clear
subscriptions) rather than unions, and ensure other fields keep the existing
update logic (runtime, other metadata) while using the entry.channels from the
snapshot to fully replace agent.channels.

---

Outside diff comments:
In `@packages/sdk/src/relay.ts`:
- Around line 1488-1497: The ensureAgentHandle function currently defaults
runtime to 'pty' which causes existing agent handles to be overwritten when
callers omit runtime; change the signature so runtime is optional (no default)
and modify the logic so updateAgentHandle(existing, ...) only applies a new
runtime when the caller passed one (i.e. check for runtime !== undefined), while
when creating a new Agent use runtime ?? 'pty' to fall back to PTY; update both
ensureAgentHandle and the corresponding update path (and the other similar block
around lines 1504-1515) to preserve existing runtime unless an explicit runtime
was provided.

---

Nitpick comments:
In `@packages/sdk/src/__tests__/lifecycle-hooks.test.ts`:
- Around line 218-233: The test is forcing transport to 'headless' which masks
the behavior the hook should set; remove the explicit transport property from
the spawnProvider call (the call to spawnProvider with name: 'patched-headless'
/ provider: 'custom-provider') and also remove the transport assertion from the
expect(captures[0].body) object so the test verifies the hook-added harnessPlan
(sessionId: 'ses_hook', runtime: 'headless', driver: 'app_server') and that
spawnProvider/beforeAgentSpawn logic flips transport indirectly via harnessPlan
rather than being pre-set by the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e823841-65e4-4c7c-9c89-2fe42ece56d1

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4ee74 and 29c9e3a.

📒 Files selected for processing (12)
  • .trajectories/completed/2026-05/traj_0d1efjk6aeo2.json
  • .trajectories/completed/2026-05/traj_0d1efjk6aeo2.md
  • .trajectories/index.json
  • crates/broker/src/protocol.rs
  • crates/broker/src/runtime/app_server.rs
  • crates/broker/src/worker.rs
  • docs/harness-runtime-plan.md
  • packages/sdk/src/__tests__/harness.test.ts
  • packages/sdk/src/__tests__/lifecycle-hooks.test.ts
  • packages/sdk/src/client.ts
  • packages/sdk/src/harness.ts
  • packages/sdk/src/relay.ts
✅ Files skipped from review due to trivial changes (4)
  • .trajectories/completed/2026-05/traj_0d1efjk6aeo2.json
  • .trajectories/completed/2026-05/traj_0d1efjk6aeo2.md
  • docs/harness-runtime-plan.md
  • .trajectories/index.json

Comment thread packages/sdk/src/harness.ts Outdated
Comment thread packages/sdk/src/relay.ts
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 12 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/sdk/src/relay.ts">

<violation number="1" location="packages/sdk/src/relay.ts:1512">
P2: `updateAgentHandle` cannot clear channels, so agent handles can retain stale channel subscriptions when the broker reports `[]`.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/sdk/src/relay.ts Outdated
@willwashburn
Copy link
Copy Markdown
Member Author

Addressed the harness-runtime review follow-ups in 8c429f2: broker now tracks/report harness PID separately from wrapper worker PID, app-server plans validate protocol/auth/attached host settings at spawn, app-server release has enough grace for the 30s HTTP timeout, app-server delivery no longer emits delivery_verified, and harness docs/examples now call out permission-flag ownership and env allowlisting. Validation: cargo check -p agent-relay-broker; cargo test -p agent-relay-broker app_server; cargo test -p agent-relay-broker harness; npm --prefix packages/sdk run check; npm --prefix web run build.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 16 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/broker/src/runtime/app_server.rs">

<violation number="1" location="crates/broker/src/runtime/app_server.rs:153">
P2: `delivery_injected` now fires after the HTTP call but still reports the pre-request timestamp, which makes injected timing inaccurate.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread crates/broker/src/runtime/app_server.rs Outdated
@willwashburn
Copy link
Copy Markdown
Member Author

Renamed the harness output terminology in 21a480e: SDK types/fields now use harnessConfig / ResolvedHarnessConfig / PtyHarnessConfig / HeadlessAppServerHarnessConfig, docs refer to broker-executable harness configs, and the design file is now docs/harness-runtime-config.md. The broker still accepts legacy harnessPlan/harness_plan JSON aliases so older payloads do not fail while the public SDK/docs use the new naming. Validation: cargo check -p agent-relay-broker; cargo test -p agent-relay-broker harness; cargo test -p agent-relay-broker app_server; npm --prefix packages/sdk run check; npx vitest run --config vitest.config.ts src/tests/harness.test.ts src/tests/lifecycle-hooks.test.ts from packages/sdk; npm --prefix web run build.

@willwashburn
Copy link
Copy Markdown
Member Author

Added a docs-specific terminology pass in c2629f8: the Harnesses page now defines Harness definition, Harness resolver, Harness config, and the one-off harnessConfig field before examples; the runtime design note has the same naming model and uses resolveHarnessConfig. I also searched docs/content for old harnessPlan / harness plan wording; remaining harnessPlan references are only broker compatibility aliases/tests. Validation: npm --prefix web run build.

@willwashburn
Copy link
Copy Markdown
Member Author

Fixed the clippy failures in e02a295. Changes: boxed AgentSpec in the large protocol enum variants without changing serialized JSON shape, replaced the manual env suppression contains check, and moved runtime util tests after non-test items. Validation: cargo clippy -p agent-relay-broker --all-targets -- -D warnings; cargo test -p agent-relay-broker harness; cargo test -p agent-relay-broker app_server.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/sdk/src/__tests__/lifecycle-hooks.test.ts (1)

218-233: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test does not actually prove transport recomputation from patched harnessConfig

Line 221 sets transport: 'headless' up front, so this test still passes without recomputing transport after hook patches. Remove explicit transport from input so the assertion at Line 227 validates recomputation behavior.

Suggested test adjustment
     await client.spawnProvider({
       name: 'patched-headless',
       provider: 'custom-provider',
-      transport: 'headless',
     });

     expect(captures[0].body).toMatchObject({
       name: 'patched-headless',
       cli: 'custom-provider',
       transport: 'headless',
       harnessConfig: {
         runtime: 'headless',
         driver: 'app_server',
         sessionId: 'ses_hook',
       },
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/__tests__/lifecycle-hooks.test.ts` around lines 218 - 233,
The test currently passes transport explicitly so it doesn't verify
recomputation; update the test that calls client.spawnProvider (in
packages/sdk/src/__tests__/lifecycle-hooks.test.ts) to remove the explicit
transport field from the input object (leave name:'patched-headless' and
provider:'custom-provider' but omit transport), so that transport must be
derived from the patched harnessConfig applied by the hook; keep the assertion
on captures[0].body.transport === 'headless' and ensure the harnessConfig patch
(sessionId:'ses_hook', runtime:'headless', driver:'app_server') is still applied
by the hook so the test verifies recomputation.
crates/broker/src/listen_api.rs (1)

652-671: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat explicit null harness configs as absent.

An explicit "harnessConfig": null currently returns 400 because key presence takes the parse branch, even though this field is optional. That's a common payload shape from JS clients and should behave the same as omission.

Suggested fix
     let harness_config = match body
         .get("harness_config")
         .or_else(|| body.get("harnessConfig"))
         .or_else(|| body.get("harness_plan"))
         .or_else(|| body.get("harnessPlan"))
         .cloned()
     {
+        Some(Value::Null) => None,
         Some(value) => match serde_json::from_value::<ResolvedHarnessConfig>(value) {
             Ok(config) => Some(config),
             Err(error) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/broker/src/listen_api.rs` around lines 652 - 671, The current
harness_config branch treats an explicit JSON null as a present value and
attempts to parse it, producing a 400; update the match handling for the cloned
value from body.get(...).cloned() so that if value.is_null() it is treated as
None (absent) instead of attempting serde_json::from_value. In other words, in
the Some(value) arm of the harness_config handling (the block that calls
serde_json::from_value::<ResolvedHarnessConfig>(value)), first check
value.is_null() and return None when true; only call from_value when value is
non-null and retain the existing error response on parse failure.
crates/broker/src/protocol.rs (1)

343-344: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix BrokerEvent::AgentSpawned.session_id wire name + omit-None behavior

  • With #[serde(tag = "kind", rename_all = "snake_case")], session_id will serialize as session_id (not sessionId), so SDK consumers expecting sessionId won’t match.
  • #[serde(default)] session_id: Option<String> will serialize None as null; if the contract expects the field to be omitted when absent, add skip_serializing_if = "Option::is_none".
Suggested fix
-        #[serde(default)]
+        #[serde(
+            default,
+            rename = "sessionId",
+            alias = "session_id",
+            skip_serializing_if = "Option::is_none"
+        )]
         session_id: Option<String>,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/broker/src/protocol.rs` around lines 343 - 344, The
BrokerEvent::AgentSpawned.session_id field currently serializes as snake_case
and emits nulls; update its serde attributes to force the wire name to
"sessionId" and omit None values by adding serde(rename = "sessionId",
skip_serializing_if = "Option::is_none") (you can keep serde(default) if needed
for deserialization), i.e., adjust the attributes on the session_id field so
consumers receive "sessionId" and absent values are omitted.
crates/broker/src/runtime/app_server.rs (1)

168-189: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t mark ambiguous prompt_async POST failures as retryable.

send_opencode_prompt() is a non-idempotent POST, but this error branch always emits retryable: true. If the request times out or the connection drops after the app-server has already accepted the prompt, the broker can replay the same delivery and inject it twice into the session. Please either add an idempotency key derived from delivery_id/event_id, or make these transport failures non-retryable until the downstream API can dedupe them.

Also applies to: 285-302

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/broker/src/runtime/app_server.rs` around lines 168 - 189, The error
branch that reports delivery failures currently marks them as retryable even
though send_opencode_prompt (a non-idempotent POST) may have already been
accepted; update the worker_error payload emitted by the delivery failure path
(the send_frame call that sends "worker_error") to set "retryable": false (or
alternatively add an idempotency key derived from delivery_id/event_id and
include it in the payload) so transports do not blindly retry ambiguous POST
failures; apply the same change to the other identical branch around the 285-302
area; look for the send_frame calls that emit "delivery_failed" and the
subsequent send_frame that emits "worker_error" to locate where to change the
payload.
♻️ Duplicate comments (2)
packages/sdk/src/harness.ts (1)

215-233: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Expand ~\... before the direct-path fast path.

resolveCommand() still returns early for Windows-style home-relative paths because expandHome() only rewrites ~/.... A command like ~\bin\claude is therefore passed through unexpanded.

Possible fix
 function expandHome(value: string): string {
   if (value === '~') return homedir();
-  if (value.startsWith('~/')) return path.join(homedir(), value.slice(2));
+  if (value.startsWith('~/') || value.startsWith('~\\')) {
+    return path.join(homedir(), value.slice(2));
+  }
   return value;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/harness.ts` around lines 215 - 233, resolveCommand currently
expands home only for POSIX style '~/...', so Windows-style home-relative paths
like '~\bin\claude' slip through unexpanded; update expandHome to also handle
backslash prefixes (e.g., check startsWith('~\\') and return
path.join(homedir(), value.slice(2))) while keeping the existing '~' and '~/’
behavior so resolveCommand's direct-path fast path sees an expanded path; modify
the expandHome function to cover both '~/...' and '~\...' cases.
packages/sdk/src/relay.ts (1)

1193-1198: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sync listAgents() channels to the broker snapshot.

updateAgentHandle() still unions channels and ignores empty snapshots. When listAgents() refreshes an existing handle after an unsubscribe, stale channels remain on agent.channels, which can also send resolveChannel() to the wrong target.

Possible fix
-        this.updateAgentHandle(existing, entry.runtime, entry.channels, entry);
+        this.updateAgentHandle(existing, entry.runtime, entry.channels, entry, true);
         return existing;
       }
@@
   private updateAgentHandle(
     agent: Agent,
     runtime: AgentRuntime,
     channels: string[],
-    metadata?: { sessionId?: string; pid?: number }
+    metadata?: { sessionId?: string; pid?: number },
+    replaceChannels = false
   ): void {
     const internal = agent as InternalAgent;
     internal._setRuntime(runtime);
-    if (channels.length > 0) {
+    if (replaceChannels) {
+      internal._setChannels([...channels]);
+    } else if (channels.length > 0) {
       internal._setChannels([...new Set([...agent.channels, ...channels])]);
     }
     internal._setMetadata(metadata);
   }

Also applies to: 1504-1516

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/relay.ts` around lines 1193 - 1198, listAgents() currently
refreshes existing handles by calling updateAgentHandle() which unions channels
and ignores empty snapshots, leaving stale channels after an unsubscribe; change
the update logic so that updateAgentHandle(existing, entry.runtime,
entry.channels, entry) replaces existing.channels with the snapshot (even if
empty) rather than unioning, and ensure updateAgentHandle and any code that uses
agent.channels (e.g., resolveChannel()) treat the provided channels array as
authoritative so stale channels are removed when entry.channels is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/broker/src/pty.rs`:
- Around line 464-466: The child_pid() accessor currently returns only the
spawn-time cached self.child_pid which can be stale; change child_pid() to, when
self.child_pid is None, call the existing process_id() (the same mechanism used
by has_exited()) to attempt to discover a PID discovered later, update the
cached self.child_pid with the discovered value, and then return the Option<u32>
so downstream fields like worker_ready.pid get populated when possible; use the
same helper logic/pattern as has_exited() to avoid duplication.

In `@crates/broker/src/worker.rs`:
- Around line 898-906: validate_app_server_config() currently allows plain
"http" endpoints and will attach credentials; change the validation around
parsed_endpoint/scheme so that if the URL scheme is "http" you reject it unless
the host is loopback (e.g., host is "localhost", "127.0.0.1" or "::1");
additionally, if any authentication is configured on the app server (e.g.,
config.auth, config.basic_auth, or whatever field holds bearer/basic creds),
always require "https" and bail with an error mentioning config.endpoint when
auth is present and scheme != "https"; apply the same guard to the other
validation branch that inspects the endpoint (the spawn-related code referenced
nearby) so credentials are only sent over HTTPS or to loopback HTTP.

---

Outside diff comments:
In `@crates/broker/src/listen_api.rs`:
- Around line 652-671: The current harness_config branch treats an explicit JSON
null as a present value and attempts to parse it, producing a 400; update the
match handling for the cloned value from body.get(...).cloned() so that if
value.is_null() it is treated as None (absent) instead of attempting
serde_json::from_value. In other words, in the Some(value) arm of the
harness_config handling (the block that calls
serde_json::from_value::<ResolvedHarnessConfig>(value)), first check
value.is_null() and return None when true; only call from_value when value is
non-null and retain the existing error response on parse failure.

In `@crates/broker/src/protocol.rs`:
- Around line 343-344: The BrokerEvent::AgentSpawned.session_id field currently
serializes as snake_case and emits nulls; update its serde attributes to force
the wire name to "sessionId" and omit None values by adding serde(rename =
"sessionId", skip_serializing_if = "Option::is_none") (you can keep
serde(default) if needed for deserialization), i.e., adjust the attributes on
the session_id field so consumers receive "sessionId" and absent values are
omitted.

In `@crates/broker/src/runtime/app_server.rs`:
- Around line 168-189: The error branch that reports delivery failures currently
marks them as retryable even though send_opencode_prompt (a non-idempotent POST)
may have already been accepted; update the worker_error payload emitted by the
delivery failure path (the send_frame call that sends "worker_error") to set
"retryable": false (or alternatively add an idempotency key derived from
delivery_id/event_id and include it in the payload) so transports do not blindly
retry ambiguous POST failures; apply the same change to the other identical
branch around the 285-302 area; look for the send_frame calls that emit
"delivery_failed" and the subsequent send_frame that emits "worker_error" to
locate where to change the payload.

In `@packages/sdk/src/__tests__/lifecycle-hooks.test.ts`:
- Around line 218-233: The test currently passes transport explicitly so it
doesn't verify recomputation; update the test that calls client.spawnProvider
(in packages/sdk/src/__tests__/lifecycle-hooks.test.ts) to remove the explicit
transport field from the input object (leave name:'patched-headless' and
provider:'custom-provider' but omit transport), so that transport must be
derived from the patched harnessConfig applied by the hook; keep the assertion
on captures[0].body.transport === 'headless' and ensure the harnessConfig patch
(sessionId:'ses_hook', runtime:'headless', driver:'app_server') is still applied
by the hook so the test verifies recomputation.

---

Duplicate comments:
In `@packages/sdk/src/harness.ts`:
- Around line 215-233: resolveCommand currently expands home only for POSIX
style '~/...', so Windows-style home-relative paths like '~\bin\claude' slip
through unexpanded; update expandHome to also handle backslash prefixes (e.g.,
check startsWith('~\\') and return path.join(homedir(), value.slice(2))) while
keeping the existing '~' and '~/’ behavior so resolveCommand's direct-path fast
path sees an expanded path; modify the expandHome function to cover both '~/...'
and '~\...' cases.

In `@packages/sdk/src/relay.ts`:
- Around line 1193-1198: listAgents() currently refreshes existing handles by
calling updateAgentHandle() which unions channels and ignores empty snapshots,
leaving stale channels after an unsubscribe; change the update logic so that
updateAgentHandle(existing, entry.runtime, entry.channels, entry) replaces
existing.channels with the snapshot (even if empty) rather than unioning, and
ensure updateAgentHandle and any code that uses agent.channels (e.g.,
resolveChannel()) treat the provided channels array as authoritative so stale
channels are removed when entry.channels is empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fce1a56-8956-4b1d-baba-b4fe3b3542db

📥 Commits

Reviewing files that changed from the base of the PR and between 29c9e3a and 342833d.

📒 Files selected for processing (30)
  • .trajectories/completed/2026-05/traj_9dj3qiugt26j.json
  • .trajectories/completed/2026-05/traj_9dj3qiugt26j.md
  • .trajectories/completed/2026-05/traj_q2r3c9dmdep7.json
  • .trajectories/completed/2026-05/traj_q2r3c9dmdep7.md
  • .trajectories/index.json
  • CHANGELOG.md
  • crates/broker/src/cli/mod.rs
  • crates/broker/src/listen_api.rs
  • crates/broker/src/protocol.rs
  • crates/broker/src/pty.rs
  • crates/broker/src/pty_worker.rs
  • crates/broker/src/runtime/api.rs
  • crates/broker/src/runtime/app_server.rs
  • crates/broker/src/runtime/mod.rs
  • crates/broker/src/runtime/relaycast_events.rs
  • crates/broker/src/runtime/spawn_spec.rs
  • crates/broker/src/runtime/tests.rs
  • crates/broker/src/runtime/worker_events.rs
  • crates/broker/src/supervisor.rs
  • crates/broker/src/worker.rs
  • docs/harness-runtime-config.md
  • packages/sdk/src/__tests__/harness.test.ts
  • packages/sdk/src/__tests__/lifecycle-hooks.test.ts
  • packages/sdk/src/client.ts
  • packages/sdk/src/harness.ts
  • packages/sdk/src/lifecycle-hooks.ts
  • packages/sdk/src/protocol.ts
  • packages/sdk/src/relay.ts
  • packages/sdk/src/types.ts
  • web/content/docs/harnesses.mdx
✅ Files skipped from review due to trivial changes (5)
  • .trajectories/completed/2026-05/traj_9dj3qiugt26j.json
  • .trajectories/index.json
  • .trajectories/completed/2026-05/traj_q2r3c9dmdep7.json
  • CHANGELOG.md
  • web/content/docs/harnesses.mdx

Comment thread crates/broker/src/pty.rs
Comment thread crates/broker/src/worker.rs
@willwashburn
Copy link
Copy Markdown
Member Author

Made headless harness driver optional in 75f019c. When runtime is headless, the broker defaults missing driver to app_server during deserialization, and the SDK static resolver defaults missing driver to app_server as well. Docs/examples now omit driver for OpenCode headless configs. Validation: cargo check -p agent-relay-broker; npm --prefix packages/sdk run check; npm --prefix web run build; cargo test -p agent-relay-broker harness; cargo test -p agent-relay-broker app_server; cargo clippy -p agent-relay-broker --all-targets -- -D warnings; targeted SDK vitest for harness/lifecycle hooks.

@willwashburn
Copy link
Copy Markdown
Member Author

Updated the PR to remove the durable SDK resolver shape and make harnesses broker-executable data instead.

Changes pushed in 4fdda79:

  • Added broker in-memory harness registry: PUT /api/harnesses/:name, GET /api/harnesses, and spawn-time harnessId resolution.
  • SDK now registers named harness configs with the broker and sends harnessId for named spawns; inline harnessConfig remains the portable per-spawn path.
  • Relaycast spawn handling now accepts harnessId or inline harnessConfig and rejects payloads that provide both.
  • Removed attached dynamic resolver guidance from the docs and rewrote the harnesses page around Claude, Codex, and OpenCode examples.

Validation: cargo check/clippy/tests for broker harness/listen_api/app_server/relaycast selection, SDK typecheck and harness/lifecycle tests, and web build. The web build succeeded but logged sandbox DNS warnings for api.github.com fetches during static generation.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 20 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/sdk/src/relay.ts Outdated
@willwashburn
Copy link
Copy Markdown
Member Author

Agreed on the footgun. I removed the broker-local harness registry and pushed e86f7d0.

Current shape:

  • SDK named harnesses are SDK-side shortcuts only; before spawn, the SDK resolves them into inline harnessConfig.
  • Broker API accepts concrete harnessConfig and rejects harnessId so old/ambiguous payloads fail loudly instead of silently falling back.
  • Relaycast agent-crafted spawns likewise require inline harnessConfig for custom harness behavior and reject harnessId.
  • Removed /api/harnesses routes, SDK client register/list methods, and the registry state from the broker runtime.
  • Docs now explicitly say spawn requests should be self-contained, especially in multi-broker environments.

Validation rerun: SDK typecheck + harness/lifecycle vitest, cargo check, clippy, broker harness/listen_api/app_server/relaycast tests, and web build.

@willwashburn
Copy link
Copy Markdown
Member Author

Resolved the merge conflicts by merging origin/main into codex/harness-runtime-plans and pushed af884f59.

Review follow-ups addressed in that push:

  • removed SDK searchPaths/existsSync command rewriting so named harnesses do not serialize caller-local resolved command paths;
  • expanded ~\\... home-relative command paths and added coverage;
  • made listAgents() treat broker channel snapshots as authoritative while keeping event updates merge-based;
  • updated app-server delivery_injected timestamps to use post-delivery time and made ambiguous delivery failures non-retryable;
  • made PtySession::child_pid() re-query the child process for late PID discovery;
  • rejected authenticated non-loopback http:// app-server endpoints and added a validation test.

Validation run:

  • cargo check -p agent-relay-broker
  • cargo clippy -p agent-relay-broker --all-targets -- -D warnings
  • cargo test -p agent-relay-broker listen_api
  • cargo test -p agent-relay-broker relaycast_harness_config
  • cargo test -p agent-relay-broker app_server
  • cargo test -p agent-relay-broker harness
  • npm --prefix packages/sdk run check
  • npx vitest run --config vitest.config.ts src/__tests__/harness.test.ts src/__tests__/lifecycle-hooks.test.ts
  • npm --prefix web run build

GitHub now reports the branch is no longer dirty; PR is blocked only by review/check requirements.

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented May 25, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Rename/move trajectory files from the top-level .trajectories tree into the .agentworkforce/trajectories directory, preserving compacted and completed subdirectories and filenames. This reorganizes storage of JSON/MD/trace files and includes the addition of traj_9d5mdl0oj0ao.{json,md} in completed/2026-05.
@willwashburn willwashburn merged commit 7afcca8 into main May 26, 2026
39 checks passed
@willwashburn willwashburn deleted the codex/harness-runtime-plans branch May 26, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant