Repository navigation
Tags: strands-labs/robots
Tags
feat(simulation): run_multi_policy takes fast_mode, as run_policy does ( #4672) * feat(simulation): run_multi_policy takes fast_mode, as run_policy does * changelog: name the fragment for #4672 * fix(simulation): describe() advertises run_multi_policy's fast_mode --------- Co-authored-by: strands-agent <217235299+strands-agent@users.noreply.github.com>
fix(ci): the release workflow grants test-lint the scope its guards a… …sk for (#3831) * fix(ci): the release workflow grants test-lint the scope its guards ask for A called workflow may not hold a scope its caller did not grant, and the mismatch is a startup failure with no job to read. #3822 gave test-lint's job pull-requests: read for the closing-reference guard and granted it from ci.yml; pypi-publish-on-release.yml, the other caller, was not updated. The v0.5.2 release fired it at 07:05:33Z and it never ran: the tag reached GitHub and not PyPI (pypi.org still serves 0.5.1). * test(ci): every reusable-workflow call site grants the scopes its callee declares test-lint.yml's job asks for pull-requests: read (#3822). A caller's permissions block is a ceiling, so a call site that does not grant it makes GitHub refuse to start the run - startup_failure, zero jobs, nothing to read. ci.yml was updated with the callee; pypi-publish-on-release.yml, the other caller, was not, so the v0.5.2 tag reached GitHub and never reached PyPI. The existing pin named ci.yml as the caller, which is how the second one went unnoticed. The call sites are discovered instead, and each is graded against the scopes every job of its callee declares. --------- Co-authored-by: strands-agent <217235299+strands-agent@users.noreply.github.com>
fix(inference): a client timeout that names no wait budget is refused… … rather than blamed on the server (#1985) Closes #1984 All four remote-inference timeout knobs - RemotePolicy.connect_timeout/request_timeout over WebSocket and the LerobotAsyncPolicy pair over gRPC - now share the positive_finite_number_error domain, so an unusable wait budget (0, negative, bool, nan, inf, non-numeric) is refused at construction with a ValueError naming the class, parameter and domain. Previously 0 / -1 / True were misattributed: TimeoutError and grpc.RpcError fall inside the clause that composes "could not reach a PolicyServer ... Start one first", so an operator was told to start a server that was already running. nan / inf / '10' escaped that clause entirely and surfaced mid-rollout as OverflowError / TypeError from transport internals naming no parameter. Over gRPC, 0 was load-dependent - it passed against a server answering instantly and failed against one taking a second. Guard placement is pinned by tests on both clients: after the port guard so the narrower refusal wins, before any transport import so the report is identical with and without the [lerobot-async] extra, and not folded into the endpoint condition. inf is refused rather than read as "no deadline" because neither transport honours it and they fail in opposite directions; premise tests re-open that decision loudly if a transport starts honouring it. tests/test_remote_client_timeout_domain.py - 132 cases, 97 of which fail on pre-fix code, including a real loopback PolicyServer dialled successfully at 5.0 and refused at 0 on the same endpoint.
docs(process): a mutation's node ID is checkable before the write (cl… …oses #1916) (#1922) AGENTS.md > PR Workflow > step 8 covers a mutation reporting the wrong thing - a mergePullRequest that says "not mergeable" on a merge that landed - and not a mutation addressing the wrong thing, which is the direction with no undo. A mutation names its subject by node ID and by nothing else: createIssue takes a repositoryId, not an owner and a name, so a well-formed ID that is wrong does not fail. It succeeds against whatever object it does name. A repositoryId carried over from an earlier response rather than queried, R_kgDOD1WOFw for R_kgDORUMiZg, created issue #1 in an unrelated third-party repository and returned success; the only clue was the url in the response, and deleteIssue needs admin on the target, so it could not be undone. The issue proposed recording the rule "next to the existing use_github guidance". There is none - AGENTS.md has no use_github, node-ID or GraphQL passage at all - so this lands as the last bullet of step 8, whose subject is already verifying a mutation by reading it back. It also corrects the premise the incident was written up under. The ID is not opaque: it is <TypePrefix>_<urlsafe-base64(msgpack array)>, a repository being [0, databaseId] and anything a repository owns [0, repository databaseId, own databaseId], so the type and the target repository are both readable with no network call. That turns "always query the ID" - advice a stale value defeats, which is what happened - into a check runnable on the value in hand. One finding the write-up did not have: all three guessed IDs in that run carried one wrong repository (257265175 against this repository's 1162027622), so a single stale value contaminated every mutation, and the two that failed did so only because their own databaseId happened not to exist there. Failing closed was luck about the guess rather than a property of the API. tests/test_graphql_node_id_targeting.py executes the claim instead of asserting the prose says it, decoding this repository's own node ID and those of an issue and a pull request in it against the databaseIds the API publishes beside them, so guidance that has become impossible fails loudly rather than reading plausibly. Writing that decoder surfaced a second permissiveness worth pinning: base64.urlsafe_b64decode discards bytes outside the alphabet and everything after the padding, so "kgDORUMiZg==extra" decodes to this repository's id. The decoder re-encodes and compares rather than relaxing the test. Documentation and test only; no production code changes. Closes #1916 Co-authored-by: strands-agent <217235299+strands-agent@users.noreply.github.com>
ci: enforce a minimum test-coverage threshold (--cov-fail-under=80) (#… …947) Coverage is already measured (--cov=strands_robots --cov-report=term-missing) but nothing defends it, so it can silently regress. This adds a floor via --cov-fail-under=80 so a PR that drops coverage below the bar fails the test job. The floor is set to 80 with deliberate headroom below the current measured level (a full local run reports ~85% total), so it protects the existing coverage without red-lighting main or forcing test-theater on hard-to-cover GPU/hardware paths. Raise it over time as coverage climbs. Confirm the exact number against the CI [all,dev] environment before merge; it can be bumped to match. Co-authored-by: Sundar Raghavan <sdraghav@amazon.com>