fix(s3): eng+dx review — 10 hardening fixes (T1-T9 + T2-b) - #209
Open
robertooliveira38 wants to merge 26 commits into
Open
fix(s3): eng+dx review — 10 hardening fixes (T1-T9 + T2-b)#209robertooliveira38 wants to merge 26 commits into
robertooliveira38 wants to merge 26 commits into
Conversation
…lhado (mirror do CubeFlow) Task 2 do plano P2. Mesmo schema/contrato de CubeFlow/src/coordination/schema.sql (tabelas driver_lock + events) — os dois processos abrem o mesmo arquivo .db. BEGIN IMMEDIATE manual (isolation_level=None) já que sqlite3 stdlib não tem o wrapper automático do better-sqlite3. Path do DB vem de CUBEFLOW_COORDINATION_DB (obrigatório, RuntimeError claro se ausente). 9/9 testes passando. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Task 3 do plano P2. Usado por CubeFlow/scripts/verify-peer-coordination.sh pra provar mutual exclusion real entre o processo Node (CubeFlow) e este processo Python contra o mesmo arquivo SQLite compartilhado. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ect) Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
FASE 1 — Foundation:
- persistence/task_store.py: TaskStore SQLite (WAL, busy_timeout, sync=NORMAL),
lifecycle PENDING/RUNNING/RETRYING/COMPLETED/FAILED/CANCELLED, idempotency_key
+ INSERT OR IGNORE + get_by_idempotency, cancel_pending, recover_orphans reaper
- retry/retry_decorator.py: @retry_with_backoff (sync+async, exp backoff+jitter,
RetryExhausted)
- logging_utils/structured_logger.py: JSON logging via stdlib + contextvars
context_id correlation
FASE 2 — Orchestration:
- orchestration/dag_executor.py: DAGExecutor (DFS cycle detect, NodeStatus,
skip dependents of failed, fail_fast/continue, sync + async per-layer)
- orchestration/scheduler.py: TaskScheduler APScheduler wrapper (import guard,
cron/interval/one-shot)
- api/main.py: FastAPI create_app factory — POST /tasks (idempotency header
X-Idempotency-Key), GET /tasks/{id}, GET /tasks, POST /workflows (DAG via
registered handlers), GET /health; bounded ThreadPoolExecutor; graceful
shutdown (shutting_down flag + cancel_pending + pool drain)
FASE 3 — Observability & intelligence:
- observability/metrics.py: ObservabilityManager singleton (thread-safe),
Prometheus shim, track_task/track_action, measure_duration, Slack alert
- orchestration/fallback.py: FallbackManager (named strategies, sync+async,
retry-of-approach contract documented)
- grounding/screenshot_cache.py: ScreenshotCache (sha256, disk LRU+TTL,
get/put/get_or_compute)
Integrations (surgical):
- grounding.py generate_coords: coords cached by screenshot hash + ref_expr
(skips LLM call on identical screen)
- worker.py: FallbackManager on plan-code eval failure (retry_ground -> wait)
- cli_app.py: track_action("exec", ok/fail) around action execution
Verified: 27/27 debug checks (cache, fallback, observability, generate_coords
cache wiring, API idempotency/shutdown/workflows/regressions). API v0.3.0,
zero new runtime deps for core (apscheduler/prometheus_client optional).
Co-Authored-By: Claude <noreply@anthropic.com>
Contêiner Docker efêmero (python:3.11-slim) p/ executar código Python/Bash gerado por LLM de forma isolada, sem tocar o host. - run_python / run_bash: bind-mount volume temp, captura stdout/stderr/exit - hardening: network_disabled, cap_drop=ALL, no-new-privileges, mem_limit - timeout com kill forçado; GC auto-destroy contêiner + volume - import-guard (docker SDK): módulo carrega sem dep; erro só ao instanciar - ExecutionResult dataclass (success/exit_code/stdout/stderr/duration/timed_out) - type hints + exception handling + structured logging (desktopenv.agent.docker) Aguarda MÓDULO 2 (VectorMemory/ChromaDB) e MÓDULO 3 (SelfHealingEngine). Co-Authored-By: Claude <noreply@anthropic.com>
Memória semântica persistente — agente recorda trajetórias de sucesso e reusa experiência quando tarefa nova é semanticamente parecida (case-based reasoning). - save_success_trajectory(task_description, steps_taken, metadata) → id - query_similar_experience(task_description, top_k=1) → List[MemoryEntry] - ChromaDB PersistentClient (~/Agent-S/data/vector_memory/), sobrevive entre sessões, sem servidor - Embeddings pluggable: provider="local" (sentence-transformers all-MiniLM-L6-v2, offline, default) | provider="openai" (text-embedding-3-small, requer OPENAI_API_KEY) - MemoryEntry dataclass (id/task_description/steps_taken/score/metadata) - score = similaridade [0,1] convertida da cosine distance - import-guard chromadb; embedding wrappers c/ name()/embed_query/embed_documents (ChromaDB ≥0.5 protocol) - type hints + exception handling + structured logging (desktopenv.agent.memory) - 10/10 smoke checks: save/query/ranking/empty/validation Aguarda MÓDULO 3 (SelfHealingEngine VLM). Co-Authored-By: Claude <noreply@anthropic.com>
Auto-correção causal: quando ação falha, engine feeda VLM (GPT-4o/Claude)
c/ ação + erro + screenshot antes/depois, pede diagnóstico estruturado
(o que mudou, causa raiz, próxima ação lógica) → action JSON p/ Worker.
- diagnose(action, error, screenshot_before, screenshot_after) → HealingResult
- HealingResult: action(dict)/root_cause/what_changed/confidence[0,1]/raw/has_recovery
- provider="openai" (gpt-4o, response_format json_object) | "anthropic" (claude-sonnet-5)
- prompt VLM estruturado; screenshots PNG bytes → base64 (image_url / image source)
- _parse_json tolera ```json fences, prose, inválido, non-dict
- error path: VLM call falha → HealingResult action={} root_cause="VLM call failed"
- confidence clamp [0,1] + non-numeric → 0.0
- import-guard openai/anthropic; fail-fast sem API key
- type hints + exception handling + structured logging (desktopenv.agent.cognition)
- 20/20 smoke checks (mock sem network): parse/diagnise/error-path/clamp/helpers
TIER 4 SUPER AGENTE completo: MÓDULO 1 DockerExecutor + MÓDULO 2 VectorMemory
+ MÓDULO 3 SelfHealingEngine.
Co-Authored-By: Claude <noreply@anthropic.com>
Integração surgical dos 3 módulos TIER 4 — opt-in via env, default
behavior inalterado (sem deps/keys não quebra).
cli_app.py:
- AGENT_S3_USE_MEMORY=1 → _memory_query(instruction) no início do ciclo;
_memory_save(instruction, traj) ao concluir ("done")
- AGENT_S3_USE_HEALING=1 → _heal(code, error, screenshot_before/after) no
except path; se has_recovery, exec ação corrigida + continue; senão raise
- _action_to_code: HealingResult.action dict → pyautogui code string
(click/type/hotkey/scroll/wait; done/fail → None)
local_env.py:
- AGENT_S3_USE_DOCKER=1 → LocalController.run_{python,bash}_script via
DockerExecutor sandbox em vez de subprocess host; fallback gracioso
subprocess se SDK/daemon indisponíveis
- _docker_executor() lazy + fail-soft
Smoke 6/6: compiles, _action_to_code 8 casos, default subprocess, env+
docker offline → fallback. Import-guard: pytesseract stub (dep projeto).
Co-Authored-By: Claude <noreply@anthropic.com>
Outputs do /graphify: - graph.html (1.4M) — grafo interativo, browser - graph.json (1.5M) — dados crus, GraphRAG-ready - GRAPH_REPORT.md — relatório auditoria (god nodes, surprising connections, suggested questions, hyperedges, health) - manifest.json — p/ --update incremental - cost.json — tracker tokens acumulado - .graphify_labels.json — labels das 21 comunidades nomeadas Stats: 1657 nós · 2883 edges · 137 comunidades (21 nomeadas: macOS ACI Grounding, Agent-S2 Core, B-Bon Narrator, S3 Persistence, S3 Orchestrator, S3 Logging+DAG, etc). God nodes: Orchestrator(46), OSWorldACI(39), LMMAgent(34), TaskStore(32). Health: 533 dangling-endpoint (surface honest). AST free + semantic docs via subagent; imagens skipadas (glm sem vision). cache/ (2.9M regenerável) excluído do commit. Co-Authored-By: Claude <noreply@anthropic.com>
Auditoria SRE 4 pilares (recursos, concorrência, contexto, fluxo). 19 issues (1 crítico, 4 alto, 8 médio, 5 baixo). Fixes aplicados: simular-ai#15 CRÍTICO — _tier4_handler integrador fecha ciclo POST /tasks → DockerExecutor → VectorMemory → Observability (env-gated AGENT_S3_API_TIER4=1; default off preserva stub) simular-ai#1 ALTO — Docker orphan GC sob SIGKILL/OOM: labels agent_s3.owner/managed no run + reap_orphans() por label (finally não roda sob SIGKILL → reaper startup remove órfãos) simular-ai#3 ALTO — VectorMemory singleton get_vector_memory(): reusa PersistentClient + modelo embedding (~90MB), evita recarregamento por chamada + contenção lock DuckDB simular-ai#4 ALTO — pool.shutdown com timeout (AGENT_S3_SHUTDOWN_TIMEOUT=30): handler deadlock não trava mais lifespan/uvicorn simular-ai#16 ALTO — _memory_save falha observável (não silenciosa): track_action("memory_save","fail") + logger.error("memory_save_failed") Arquivos: AUDITORIA_SRE.md (tabela+snippets), stress_test_tasks.py (asyncio+aiohttp, POST /tasks concorrente, idempotência, completion poll). Smoke 7/7 verificado. Default behavior inalterado (tudo env-gated). Co-Authored-By: Claude <noreply@anthropic.com>
… reaper Aplicado da auditoria SRE (issues médias): simular-ai#7 VLM timeout — SelfHealingEngine(vlm_timeout=30.0) passado aos clientes OpenAI/Anthropic; chamada VLM hang não bloqueia step simular-ai#10 context_id → Docker — DockerExecutor.run_{python,bash}(env=) injeta env vars no contêiner; local_env passa AGENT_S3_CONTEXT_ID p/ correlação de logs container↔host simular-ai#11 context_id → ChromaDB — _memory_save inclui context_id no metadata da trajetória (rastreabilidade) simular-ai#12 stderr JSON estruturado — local_env logger.error (docker_bash_failed/docker_python_failed) c/ context_id + exit_code + stderr[:500], em vez de só print simular-ai#2 temp dir reaper — marker .agent_s3_pid no mkdtemp; _reap_temp_dirs() + reap_orphans() limpam dirs /tmp/agent_s3_sandbox_* órfãos sob SIGKILL/OOM (finally não roda → vazamento de volume) Não aplicados (push back, documentado): simular-ai#5 ChromaDB multi-process (migration p/ server mode, não fix), simular-ai#14 logs duplicados cli_app (risco quebrar CLI logging existente). Smoke 5/5 verificado. Default behavior inalterado (env-gated). Co-Authored-By: Claude <noreply@anthropic.com>
Tabela status 19 issues: 10 aplicados (7fd43dc + 02b30df), 2 push back (simular-ai#5 ChromaDB multi-process migration, simular-ai#14 logs CLI), 1 pendente (simular-ai#17 DAG idempotency_key), 6 confirmados OK. Co-Authored-By: Claude <noreply@anthropic.com>
Finaliza auditoria SRE (issues simular-ai#17 simular-ai#14 simular-ai#5): simular-ai#17 DAG _persist isolamento por run — store_id composto f"{run_cid}:{node.task_id}" + idempotency_key=store_id. Re-run do mesmo DAG não reusa/sobrescreve rows do run anterior (1 row/run/node). Smoke: 2 runs = 2 rows distintos confirmado. simular-ai#14 logs CLI fragmentação — cli_app registra FileHandlers (file/debug/sdebug) no logger "desktopenv.agent" além do root. structured_logger set propagate=False → sem isto, logs desktopenv não chegavam a logs/*.log. Mantém handler JSON p/ stdout. simular-ai#5 ChromaDB multi-process — VectorMemory provider="remote" + env AGENT_S3_CHROMA_URL → HttpClient (conecta `chroma run` server, seguro multi-process). Default PersistentClient local preservado. Auto-promove p/ remote se env set. RuntimeError explicativo sem url. Auditoria SRE 13/19 aplicados. 6 baixos confirmados OK (race-safe/doc). Smoke 3/3. Default behavior inalterado. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
/autoplan pipeline (CEO→Eng→DX→gate) over AUDITORIA_SRE.md. Eng review found 4 real defects in shipped code; DX review found 2 onboarding gaps. All applied + verified; 1 subagent overclaim corrected (H4). Code fixes: - T1 (C2 critical gap, default path): local_env.run_python_script gained timeout=30 + TimeoutExpired handler (bash had timeout, python didn't; LLM infinite loop hung the worker thread). - T2 (H2): get_vector_memory raises ValueError on conflicting kwargs (singleton silently ignored provider/kwargs after first init). - T2-b (H2, 2nd singleton): ObservabilityManager.__init__ guard raises on conflicting metrics_port/slack_webhook_url (same silent-ignore bug). - T3 (H3): _parse_json rewritten with json.JSONDecoder().raw_decode (brace-counter broke on `}` inside string values; raw_decode is a real JSON parser, shorter + correct). - T4 (M3): DAGExecutor.execute_async wraps sync fns in run_in_executor (blocking sync fn stalled the event loop; asyncio.gather gave zero parallelism). Two 1s sync fns now run in 1.01s (was ~2s serial). - T5 (H1): _alive_pids docstring documents single-worker limit + UUID label fix for multi-worker uvicorn. - T9 (H4 nuance, user-requested): scheduled reaper — reap_orphans_now() module helper (daemon-safe, bypasses constructor) + _reaper_loop daemon thread in API lifespan, interval AGENT_S3_REAPER_INTERVAL (default 3600s), stops on shutting_down. Stdlib threading (apscheduler absent). Docs/tests: - T6: tests/test_docker_reaper.py — 4 unit tests, mock docker client (dead-owner reaped, live-owner kept, no-managed, no-SDK short-circuit). - T7: docs/TIER4.md — all tier4 prerequisites in one place. - T8: CLAUDE.md env-var reference table (6 gates). Regression: 77 pass, 6 pre-existing failures in test_grounding_computer_use.py (_coord_cache_inst AttributeError, committed c725450, not in this diff). Zero regressions introduced. Co-Authored-By: Claude <noreply@anthropic.com>
…ng.py) 6 pre-existing failures in test_grounding_computer_use.py traced to a real bug, not test rot. Root cause: tests instantiate OSWorldACI via __new__ (bypassing __init__) to avoid API-key deps in LMMAgent/CodeAgent. Commit c725450 added self._coord_cache_inst = None in __init__ (line 242) and _coord_cache() read the attribute unguarded (line 246). Objects created via __new__ never set the attribute → AttributeError: 'OSWorldACI' object has no attribute '_coord_cache_inst'. Fix: hasattr guard in _coord_cache(). Partial-init objects (no attribute) return None (cache disabled) — identical to pre-c725450 behavior. Full-init objects lazy-load as before. One method, surgical. Regression: 83/83 pass (was 77 pass + 6 fail). Zero new regressions. Co-Authored-By: Claude <noreply@anthropic.com>
… procedural memory Pre-existing working-tree changes (inspected + verified, 51/51 related tests pass). Committed per user request. engine.py (LMMEngineAnthropic): - claude-sonnet-5 compat: omit `temperature` (rejected outright by newer models regardless of value, matching the thinking-mode branch). - strip trailing assistant messages defensively (some retry/bookkeeping paths leave a dangling assistant turn last → "model does not support assistant message prefill"). - iterate response.content for the text block (sonnet-5 can prepend a ThinkingBlock even without extended thinking requested; content[0] is no longer reliably the text block). orchestrator.py (Orchestrator): - API key validation: construct Anthropic client only if key present; missing key → graceful abort (0 MCP calls) with reason missing_anthropic_api_key instead of opaque failure in _run_orchestrator. - whitespace-strip key gate: " " is truthy and defeated the `if not self._api_key` gate → orchestrator proceeded to discovery+PROBE+ReAct and failed opaquely on first messages.create. Strip closes the gap. - auto-discovery: if session has no tracks, discover via list_project_tracks before PROBE (non-destructive). - tool_sequence telemetry; taskstore integration (record/lookup). taskstore.py (new, 126 lines): procedural memory (sqlite3 stdlib, no deps). Records each run (telemetry + tool sequence) and replays the winning sequence (perfect=true) for matching signature, skipping the PLAN step on cycle 1 and only VERIFYing. Never trusts blindly — if verify not perfect, falls back to normal ReAct. Gate AGENT_S_USE_MEMORY=0 disables. Data in ~/Agent-S/data/taskstore.db. tests: conftest.py collection barrier (skip grounding if pytesseract/ tesseract absent — keeps `pytest tests/` collectible in CI). New tests: test_taskstore, test_taskstore_adversarial, test_orchestrator_adversarial, test_orchestrator_gaps, test_panel_session, test_panel_session_adversarial, test_field_real_mcp, test_grounding_computer_use. test_orchestrator_react gains hermetic setup (AGENT_S_USE_MEMORY=0) + discovery/memory tests. Verified: 51/51 orchestrator+taskstore+panel tests pass. Full suite 83/83 (including PF1 fix from e250e1c). Co-Authored-By: Claude <noreply@anthropic.com>
…loop) MÓDULO 1 — gui_agents/s3/cognition/critic_agent.py (514 linhas): CriticAgent, o "3º cérebro" cognitivo (padrão o1: planejar→executar→criticar). - research(): web search DuckDuckGo (instanciação direta DDGS(), não o context-manager quebrado do v8.x) + Tavily fallback (AGENT_S3_TAVILY_KEY). Gate heurístico _needs_research — só busca se a tarefa menciona biblioteca/versão/how-to; _build_query stripa version numbers + filler, cap 6 palavras (DDGS prefere queries curtas). - review_code(): revisão pré-execução (imports faltantes, paths hardcoded, logic bugs, APIs inexistentes, silent failures) → ReviewResult com código corrigido + lista de issues. Roda ANTES do DockerExecutor. - complete(): chamada LLM texto genérica (usada pelo Executor no CLI). - Provider pluggable openai|anthropic, import-guards _HAS_*, fail-fast no ctor, _parse_json via json.JSONDecoder().raw_decode (H3 — brace-counter quebra com } dentro de strings), timeout no HTTP client. - Degradación graciosa: falha de API → research "" / review devolve código original inalterado. O ciclo do agente nunca trava por causa do crítico. MÓDULO 2 — gui_agents/s3/cli/chat_cli.py (447 linhas): ChatCLI (rich + cmd.Cmd), loop interativo que orquestra a cadeia cognitiva em 6 steps: (1) VectorMemory consulta experiência passada, (2) CriticAgent research docs, (3) Executor LLM gera código, (4) CriticAgent review_code, (5) DockerExecutor roda (fallback subprocess host), (6) salva trajetória vencedora no VectorMemory. - Env-gates default OFF (AGENT_S3_USE_MEMORY/USE_DOCKER/CRITIC_PROVIDER). - Lazy init + sentinela False + _PlainConsole fallback se rich ausente. - default() catch-all: qualquer input vira tarefa; loop nunca morre. - _step_run mapeia DockerResult e resultado subprocess local p/ dict uniforme. requirements.txt: +rich +duckduckgo_search (+python-dotenv, pytest tooling). Verificação: 77/77 pytest pass (0 regressões), 4/4 módulos ast.parse OK. Sem unit tests próprios (LLM-dependent, env-gated); heurísticas (_build_query, _needs_research, _parse_json, _strip_fences, _detect_language) verificadas estaticamente. Cobertura gap documentada. Co-Authored-By: Claude <noreply@anthropic.com>
Author
TIER 5 — CriticAgent + ChatCLI (commit f2ddac0)Adds the 3rd cognitive brain (o1-like plan→execute→critique) and an interactive Rich+cmd CLI that orchestrates the full 6-step chain. MÓDULO 1 —
|
Fecha o coverage gap anotado no commit f2ddac0. 29 testes cobrindo o que não depende de LLM/web: - SearchResult / ReviewResult dataclasses (defaults + explícitos) - _parse_json: objeto válido, fenced json, prose-around, brace dentro de string (caso H3 — "click({x:100})"), inválido→None, vazio→None, None→None, primeiro objeto válido vence - _strip_fences: ```python...```, bare ```, no-fences passthrough, multiline dentro de fences - _needs_research: keywords (library/how to/pip install), version regex, plain task→False, empty→False - _build_query: strip version numbers (mantém nome da lib), strip filler, cap 6 palavras, drop single-char tokens - ChatCLI._detect_language: python default, bash hints (grep/bash/apt) 106/106 pytest pass (77 base + 29 novos). 0 regressões. Co-Authored-By: Claude <noreply@anthropic.com>
Bug pego no smoke-test: cmd.Cmd.parseline ignora chars não-identificador à
esquerda, então "/exit" virava cmd vazio → default() → tratado como tarefa
→ disparava CriticAgent.init → warnings de OPENAI_API_KEY e não saía.
O intro prometia "/exit" mas cmd.Cmd queria "exit" bare.
Fix: override parseline — se linha começa com / E o resto resolve p/ um
do_* registrado, usa esse comando; senão cai no parse default. Preserva
tarefas que começam com path ("/tmp/foo.pdf" → do_tmp não existe → default
→ run_task).
+6 testes TestParselineSlashRouting: /exit, /help, /memory roteiam; bare
"exit" ainda funciona; "/tmp/foo.pdf" cai em default; plain task sem
do_ method. 35/35 critic tests pass.
Co-Authored-By: Claude <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
Full review pipeline (CEO→Eng→DX→Final gate, gstack
/autoplan) overAUDITORIA_SRE.mdas the plan file. Eng review found 4 real defects in shipped code; DX review found 2 onboarding gaps. All applied + verified. 1 subagent overclaim corrected (H4).Code fixes
local_env.run_python_scriptgainedtimeout=30+TimeoutExpiredhandler. The bash path already had a timeout; the python (default) path did not — an LLM infinite loop hung the worker thread.get_vector_memoryraisesValueErroron conflicting kwargs. The singleton silently ignored provider/kwargs after first init.ObservabilityManager.__init__guard raises on conflictingmetrics_port/slack_webhook_url. Same silent-ignore bug, different singleton (__new__-based)._parse_jsonrewritten withjson.JSONDecoder().raw_decode. The old brace-counter broke on}inside string values;raw_decodeis a real JSON parser, shorter + correct.DAGExecutor.execute_asyncwraps sync fns inrun_in_executor. A blocking sync fn stalled the event loop;asyncio.gathergave zero parallelism. Two 1s sync fns now run in 1.01s (was ~2s serial)._alive_pidsdocstring documents the single-worker limit + UUID-label fix for multi-worker uvicorn.reap_orphans_now()module helper (daemon-safe, bypasses constructor) +_reaper_loopdaemon thread in the API lifespan, intervalAGENT_S3_REAPER_INTERVAL(default 3600s), stops onshutting_down. Stdlib threading (apscheduler absent).Docs / tests
tests/test_docker_reaper.py— 4 unit tests with a mock docker client (dead-owner reaped, live-owner kept, no-managed returns 0, no-SDK short-circuit). All pass.docs/TIER4.md— all tier4 prerequisites in one place.CLAUDE.mdenv-var reference table (6 gates).Regression
77 pass, 6 fail — all 6 failures are pre-existing in
test_grounding_computer_use.py(_coord_cache_instAttributeError, committed earlier in c725450, not in this diff). Zero regressions introduced.Notes
_reap_temp_dirsexists and reads the.agent_s3_pidmarker.🤖 Generated with Claude Code