mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(sandbox): on-demand provisioning via request_sandbox verb (#338)
* feat(sandbox): on-demand request_sandbox verb replaces eager provisioning Sandboxes were provisioned at every agent spawn for opted-in projects, so every role paid the sidecar spin-up and a provisioning failure refused the spawn. Provisioning now happens when an agent asks: the request_sandbox do-verb (dev + QA) reaches the orchestrator through ContentActionsDeps, ensure_sandbox provisions idempotently with an in-memory per-agent cache (evicted at teardown and janitor sweep), and creds return in the envelope payload including ready-to-export ROBOCO_TEST_* values. Spawn now only injects a marker env naming the available services plus a briefing line; sandbox failures can no longer refuse a spawn. Teardown lifecycle unchanged. * feat(sandbox): harden request_sandbox + Phase 3 wiring proof and docs Hardening from adversarial review: ensure_sandbox now provisions the project's full opted-in set on first request (a later superset can never tear down a live sandbox mid-use), serializes per-agent behind an asyncio lock (a client timeout-retry no longer races its own in-flight provision), and verifies container liveness on every cache hit (a dead sandbox evicts and re-provisions instead of serving dead creds). MCP client budget 720->1080s for the full-set cold case. Phase 3: e2e smoke wiring test (manifest grants + guard-chain envelopes over the real API), sandbox-db/tools/map docs and CLAUDE.md rewritten for on-demand. * feat(sandbox): release sandboxes when the agent's work ends CEO directive: sidecars must not dangle once the agent is done. The six work-ending verbs (i_am_done, unclaim, i_am_idle, pass_review, fail_review, i_documented) now release the caller's sandbox best-effort on their success path via release_sandbox (lock + teardown + cache evict; a no-sandbox agent costs a dict lookup). Container removal and the janitor remain the backstop; a re-request provisions fresh. * test(sandbox): monkeypatch the release hook instead of method assignment mypy method-assign rejected the direct AsyncMock assignments; the prior static gate ran before this test file landed. * test(sandbox): guard envelope evidence for mypy in verb tests * chore(prompts): regenerate verb tables for request_sandbox * chore: resolve merge with master (breadcrumbs + statement budget) --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -42,6 +42,14 @@ _TIMEOUT = 30
|
||||
# shared _TIMEOUT above is tuned for fast content-tool calls (note/dm/
|
||||
# evidence) and would give up first — client must outlast the server op.
|
||||
_COMMIT_TIMEOUT = 190
|
||||
# request_sandbox provisions inline; ensure_sandbox now always provisions the
|
||||
# project's FULL opted-in set on first call (kills the superset/teardown
|
||||
# race — see ensure_sandbox's docstring), so an all-three-cold first request
|
||||
# is the norm, not the rare case. Worst case: 3 x 300s cold pulls
|
||||
# (SandboxProvisioner._DOCKER_PULL_TIMEOUT_SECONDS) + readiness (~135s) ~=
|
||||
# 1035s — images are pre-pulled at startup in practice, so cold pulls here
|
||||
# are the exception, but the timeout must cover the worst case anyway.
|
||||
_SANDBOX_TIMEOUT = 1080
|
||||
# Tight timeout for SDK loopback — local sidecar; gateway path must not stall.
|
||||
_SDK_TIMEOUT = 2.0
|
||||
# FastAPI's default missing-route status. Every /api/v1/do/* route returns
|
||||
@@ -694,6 +702,26 @@ def evidence(task_id: str) -> dict[str, Any]:
|
||||
return _post("/api/v1/do/evidence", {"task_id": task_id})
|
||||
|
||||
|
||||
def request_sandbox(services: list[str] | None = None) -> dict[str, Any]:
|
||||
"""Provision (or reuse) a throwaway sandbox DB/Redis/Mongo for YOUR active task.
|
||||
|
||||
On-demand — nothing is provisioned at spawn. Omit ``services`` to get the
|
||||
project's whole opted-in set; requesting a service the project didn't opt
|
||||
into is rejected with the allowed set named. Creds come back in
|
||||
``evidence``, one entry per service: ``{host, port, user, password,
|
||||
database, env: {ROBOCO_TEST_*: value}}`` — export the ``env`` values
|
||||
verbatim for gate tooling that reads them. The whole opted-in set is
|
||||
provisioned on first call, so calling this again for any subset or
|
||||
superset of it is a cheap no-op (same creds, no re-provisioning); a
|
||||
project that never opted into sandbox services will reject this.
|
||||
"""
|
||||
return _post(
|
||||
"/api/v1/do/request_sandbox",
|
||||
{"services": services},
|
||||
timeout=_SANDBOX_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
def draft_playbook(
|
||||
title: str,
|
||||
problem: str,
|
||||
@@ -891,6 +919,7 @@ _TOOLS: dict[str, Any] = {
|
||||
"dm": dm,
|
||||
"notify": notify,
|
||||
"evidence": evidence,
|
||||
"request_sandbox": request_sandbox,
|
||||
"progress": progress,
|
||||
"notify_list": notify_list,
|
||||
"notify_get": notify_get,
|
||||
|
||||
Reference in New Issue
Block a user