fix(harness-runner): switch from non-existent /heartbeat-history to /activity - #2265
Merged
hongmingwang-moleculeai merged 1 commit intoApr 29, 2026
Merged
Conversation
…activity The runner was speculatively calling `/workspaces/:id/heartbeat-history` — that endpoint doesn't exist on workspace-server. On local dev it 404'd; on tenant builds the platform's :8080 canvas-proxy fallback intercepted it and returned 28KB of Next.js HTML which then landed in the JSON event log. Neither outcome was useful trace data. `GET /workspaces/:id/activity` is the existing endpoint that reads activity_logs. That table already records the events the RFC §V1.0 step 6 'platform-side transition' check needs (a2a_send / a2a_receive / task_update / agent_log / error, plus duration_ms + status). Rename the runner's fetch + emitted event accordingly. Verified: GET /workspaces/<uuid>/activity?since_secs=60 returns 200 with `[]` against the local platform; no SaaS skip needed since the endpoint exists in both environments. Refs: molecule-core#2256 (V1.0 gate #1 measurement comment).
HongmingWang-Rabbit
requested a review
from hongmingwang-moleculeai
as a code owner
April 29, 2026 06:13
This was referenced Apr 29, 2026
HongmingWang-Rabbit
pushed a commit
that referenced
this pull request
Jun 12, 2026
… id is BYOK-routable (cp#529)' (#2265) from fix/cp529-enforcer-test-unbreak-main into main
HongmingWang-Rabbit
pushed a commit
that referenced
this pull request
Jun 12, 2026
…fered runtime×provider gated (keyless, required-lane)
Closes the provider-routing-correctness coverage hole identified in the
regression-coverage audit: many offered (runtime → provider) pairs — hermes's
17 name-only BYOK arms, claude-code's zai/deepseek/xiaomi-mimo, openclaw's
byok-openai/byok-minimax/groq/openrouter/custom, codex's byok-minimax, etc. —
are pure prefix-routing resolved by DeriveProvider(runtime, modelId) and had
ZERO test. A regression in the routing table (wrong provider, dropped arm, bad
regex) shipped silently and wedged tenant agents at boot.
DeriveProvider + ModelPrefixMatch resolve a model id to a provider with NO
upstream call — fully keyless — so the ENTIRE offered routing table is gateable
in the REQUIRED CI / all-required lane with zero secrets.
derive_provider_matrix_test.go is SSOT-DRIVEN (not hardcoded): it iterates
LoadManifest().Runtimes (the same registry production reads) and, for every
runtime × every offered model/provider arm, asserts (a) DeriveProvider resolves
to the EXACT expected provider (computed from the SSOT), (b) the (runtime, model)
is registration-valid (the validateRegisteredModelForRuntime predicate), and
(c) no offered id silently resolves to the wrong arm or falls through.
- exact-listed arms: every model id iterated off the SSOT, expected provider
computed from native declaration order (first-declared wins the codex/
anthropic "one id, two auth arms" shape). A newly-added model is auto-covered.
- name-only arms (zero models, pure prefix BYOK): each probed with a
representative BYOK id its regex must own. The matrix REQUIRES a representative
for every name-only arm in the SSOT — "added an arm, forgot routing/sample"
fails RED. A dead representative (provider removed) also fails RED.
Coverage: 5 runtimes, 43 (runtime×provider) arms across 29 distinct providers,
53 exact-listed (runtime×model) assertions + 29 name-only BYOK routing probes.
Known-tricky forms pinned as explicit assertions so a regression names its class:
the #2263/#2274 colon-vs-slash-vs-bare MiniMax triple on claude-code (bare→minimax,
slash→platform, colon→unregistered), openai-namespaced-rejected-on-claude-code
(#2265 class), groq→groq, hermes anthropic//gemini//openai://minimax: →
byok-* (NOT platform — cp#529 billing safety), codex gpt default→openai-subscription
vs OPENAI_API_KEY→openai-api, google-adk platform: vs bare gemini.
Watch-it-fail proven: adding minimax:MiniMax-M2.7 to claude-code's platform arm
(pointing the colon BYOK form at platform) reds the matrix naming the exact
mismatch ("= platform, want an unregistered/unrouteable ERROR"); reverted → green.
go build ./... and go vet ./internal/providers/ clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The runner was speculatively calling
GET /workspaces/:id/heartbeat-history— that endpoint doesn't exist on workspace-server. Local dev returned 404; tenant deployments returned 28KB of Next.js HTML (canvas-proxy fallback intercepting unmatched paths). Neither was useful trace data.GET /workspaces/:id/activityis the existing endpoint that readsactivity_logs— already recordsa2a_send/a2a_receive/task_update/agent_log/errorevents withduration_ms+status, which is exactly the data RFC #2251 §V1.0 step 6 ("platform-side transition" check) needs.Test plan
bash -nsyntax check passesGET /workspaces/<uuid>/activity?since_secs=60returns 200 with[]against the local platform — endpoint exists, no SaaS skip neededheartbeat_trace→activity_tracein both the fetch and the interpretation block at the bottomLinked