feat(sdk): compose local agents with Desktop and Workstation - #222
cm2435-hcomp wants to merge 44 commits into
Conversation
|
bugbot run |
|
bugbot run |
…t.local() Hand-written runtime management lives outside the generated package, so a codegen sync cannot wipe it. Local clients are built with Client.local() and AsyncClient.local(), keeping the generated constructors and their typing.
HTTP(S)_PROXY no longer receives the local runtime bearer token.
Every request to a local runtime carries a fresh X-Hai-Runtime-Challenge, and every response must carry X-Hai-Runtime-Proof, the HMAC-SHA256 of the challenge keyed by the runtime token. Unproven responses raise LocalRuntimeError, so a server squatting the runtime port never receives the bearer token and its commands never reach a device bridge. /health is proven before any bearer-authenticated request, both when spawning and when attaching.
Closing the client that started the runtime terminated it for every attached client. close() now stops the runtime only when no session other than this client's own is still active; shutdown() still stops it unconditionally.
…to charlie/placement-sdk-pins # Conflicts: # src/hai_agents_local/runtime/manifest.py
|
Pushed review fixes directly (as agreed).
Tests: 228 passed, 12 skipped. The release gate was checked both ways: it fails on the 0.1.8 pin and passes against a runtime that serves the shared recipe. Needs a runtime release with identity proofs plus a pin bump before this can ship. |
…to charlie/placement-sdk-pins
|
Folded #223 into this PR (fast-forward, no other changes). Merge order unchanged. |
…s, validates, dumps
|
Simplification pass (6 commits, net -139 lines, no behavior change)
Tests
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f4659fd. Configure here.
| def _claim(runtime: LocalRuntime, inference: typing.Optional[Inference]) -> bool: | ||
| if inference is not None and not runtime.owned: | ||
| raise ValueError("inference selection cannot reconfigure an existing runtime; choose a free local port") | ||
| return runtime.owned |
There was a problem hiding this comment.
Attached clients never stop leftover runtime
High Severity
Client.local() only marks the original spawner as owner. If that client closes while another attached client still has sessions, shutdown_if_idle leaves the process running, and later attachers never claim ownership. No later close() stops the child, so the runtime and its credential files stay up until a manual kill.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit f4659fd. Configure here.


What
Expose local agent execution through the existing Python Agent API client, composed with local Desktop or remote Workstation environments. Own the local runtime lifecycle, authenticated attachment, command interruption and session resources; keep the hosted client default unchanged. Permission prompts run on the main thread.
Why
Each product currently has to manage its own local agent process and device bridge. The SDK should own that lifecycle behind the existing session interface.
How
Add Client(mode="local"), await AsyncClient.local(), and local runtime/process ownership, authenticated attachment, session resources and interruption through existing device bridges. Execute OS permission preflight on the main thread.
Validation and dependencies
P4; depends on P1/P2 HAI contracts/drivers/runtime and the matching P3 generation overlays. Runtime manifest defaults are preserved from the existing release, so this PR MUST NOT publish or merge until a verified compatible runtime and dependency floors are pinned. Explicit candidate overrides are for QA only.
Validation: 225 non-integration SDK tests pass, 12 skipped, on current main with the review runtime sources. Prior live QA covered local and cloud environments, file round trips, follow-ups and owned-command Stop. SDK workstation work already merged in #220 is excluded from this diff.
QA scope: real local-agent/local-environment and local-agent/cloud-environment runs cover files, follow-ups, Stop and reuse. Candidate hosted-agent combinations and packaged visual parity remain unverified. HoloWork integration is deferred.
Stack navigation: P1 contract → P2 runtime; P3 generation + P4 SDK; P5 checks: runtime, generation, SDK pins; P6 CLI. P7 duplicate pin cleanup follows only after P6 ships. HoloWork integration is subsequent work.
Review guide: intent, architecture, service tradeoffs and merge order.
CI dependency constraint: the published hai-drivers dependency lacks DesktopCommandRunner, so the real SDK/driver Stop regression fails collection in hosted CI until the compatible driver is available. It passes with candidate drivers locally; it has not been skipped to hide the dependency. Changed Python files pass Ruff lint and format.
Lifecycle refinements from review: downloads run outside the per-port spawn lock; idle probes close their HTTP pools; failed cancellation retains the exit retry; startup cleanup preserves the original error; credential cleanup uses the startup lock; asynchronous startup has an awaited, cancellation-safe factory. Regression tests exercise these boundaries.
Note
High Risk
Large new surface area: process spawn, verified binary downloads, loopback auth/HMAC, and session/bridge lifecycle that affects local desktop/workstation execution and release gating.
Overview
Adds local agent execution on top of the existing session API via
Client.local()/await AsyncClient.local(), while the defaultClient()still targets the hosted Agents API.A new
hai_agents_local.runtimelayer starts or attaches to loopbackhai-agent-runtime(PATH, explicit binary, or sha256-verified download frompin.json), enforces the shared recipe, and uses HMAC challenge/proof HTTP clients so traffic cannot be served by a squatter on the port. OptionalInferenceroutes model calls to cloud (HAI_API_KEY) or a self-hosted endpoint without leaking hosted keys. Context managersclose()/aclose()cancel client-owned sessions, stop bridges, andshutdown_if_idle()owned runtimes when no other active sessions remain.Local sessions wire bridges to the runtime token with
verify_runtime, run macOS permission preflight on the main thread before async bridge startup, and improve stop/cancel (driver interrupt on bridge stop, HTTP 410 channel closure, unconfirmed bridge stops, exit-time cancel retries). Release workflow gains aruntime-pinmacOS job andscripts/bump_runtime.pyto bump pinned runtime digests; README documents the local-agent flow.Reviewed by Cursor Bugbot for commit f4659fd. Bugbot is set up for automated code reviews on this repo. Configure here.