control-tower 6.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- control_tower/__init__.py +3 -0
- control_tower/backends/__init__.py +31 -0
- control_tower/backends/fleet/__init__.py +1 -0
- control_tower/backends/fleet/ami.py +45 -0
- control_tower/backends/fleet/aws_identity.py +9 -0
- control_tower/backends/fleet/bootstrap.sh +349 -0
- control_tower/backends/fleet/config.py +40 -0
- control_tower/backends/fleet/controller.py +2058 -0
- control_tower/backends/fleet/job.py +325 -0
- control_tower/backends/fleet/run.py +399 -0
- control_tower/backends/fleet/run_job.py +270 -0
- control_tower/backends/fleet/worker.py +1045 -0
- control_tower/backends/fleet/worker_cli.py +68 -0
- control_tower/backends/hawk/__init__.py +7 -0
- control_tower/backends/hawk/config.py +114 -0
- control_tower/backends/hawk/download.py +93 -0
- control_tower/backends/hawk/middleman.py +64 -0
- control_tower/backends/hawk/model_closure.py +39 -0
- control_tower/backends/hawk/preflight.py +142 -0
- control_tower/backends/hawk/pull_envs.py +160 -0
- control_tower/backends/hawk/runner.py +1050 -0
- control_tower/backends/hawk/sandbox.py +561 -0
- control_tower/backends/hawk/yaml_generator.py +206 -0
- control_tower/backends/local.py +223 -0
- control_tower/cli/__init__.py +3 -0
- control_tower/cli/base.py +207 -0
- control_tower/cli/cloud.py +132 -0
- control_tower/cli/completions/completion_callbacks.py +125 -0
- control_tower/cli/completions/completions_build.py +59 -0
- control_tower/cli/completions/completions_hooks.py +209 -0
- control_tower/cli/extras.py +89 -0
- control_tower/cli/login.py +77 -0
- control_tower/cli/main.py +51 -0
- control_tower/cli/mcp_meta.py +30 -0
- control_tower/cli/protocols.py +140 -0
- control_tower/cli/run.py +893 -0
- control_tower/cli/task_sets.py +123 -0
- control_tower/datasets/__init__.py +0 -0
- control_tower/datasets/check.py +88 -0
- control_tower/datasets/cli.py +599 -0
- control_tower/datasets/external.py +675 -0
- control_tower/datasets/importers/__init__.py +47 -0
- control_tower/datasets/importers/eval_log.py +596 -0
- control_tower/datasets/importers/monitoring_bench.py +766 -0
- control_tower/datasets/importers/mythos_5_incident.py +241 -0
- control_tower/datasets/importers/sessions.py +159 -0
- control_tower/datasets/importers/shade_arena.py +588 -0
- control_tower/datasets/importers/sleight_bench.py +487 -0
- control_tower/datasets/model.py +45 -0
- control_tower/environments/build.py +191 -0
- control_tower/environments/env_args.py +98 -0
- control_tower/environments/environment.py +92 -0
- control_tower/environments/internet_simulator/.dockerignore +3 -0
- control_tower/environments/internet_simulator/Dockerfile +89 -0
- control_tower/environments/internet_simulator/__init__.py +0 -0
- control_tower/environments/internet_simulator/__main__.py +4 -0
- control_tower/environments/internet_simulator/compose.yml +31 -0
- control_tower/environments/internet_simulator/constants.py +53 -0
- control_tower/environments/internet_simulator/core/assets.py +304 -0
- control_tower/environments/internet_simulator/core/exposed_services.py +71 -0
- control_tower/environments/internet_simulator/core/generate_ca.py +68 -0
- control_tower/environments/internet_simulator/core/logs.py +212 -0
- control_tower/environments/internet_simulator/core/network_mapping.py +63 -0
- control_tower/environments/internet_simulator/core/tls.py +124 -0
- control_tower/environments/internet_simulator/core/trajectory.py +17 -0
- control_tower/environments/internet_simulator/core/webhook.py +195 -0
- control_tower/environments/internet_simulator/malware/malware.c +143 -0
- control_tower/environments/internet_simulator/modules/forward_proxy.py +431 -0
- control_tower/environments/internet_simulator/modules/provider_policy.py +390 -0
- control_tower/environments/internet_simulator/modules/response_rules.py +193 -0
- control_tower/environments/internet_simulator/modules/shell_sessions.py +206 -0
- control_tower/environments/internet_simulator/modules/upstream_proxy.py +289 -0
- control_tower/environments/internet_simulator/modules/upstream_specs.py +221 -0
- control_tower/environments/internet_simulator/personas.py +150 -0
- control_tower/environments/internet_simulator/requirements.txt +3 -0
- control_tower/environments/internet_simulator/resolve_override.c +75 -0
- control_tower/environments/internet_simulator/runtimes/__init__.py +0 -0
- control_tower/environments/internet_simulator/runtimes/docker.py +630 -0
- control_tower/environments/internet_simulator/runtimes/hawk.py +193 -0
- control_tower/environments/internet_simulator/runtimes/provisioning.py +39 -0
- control_tower/environments/internet_simulator/server.py +660 -0
- control_tower/environments/internet_simulator/source_hash.py +78 -0
- control_tower/environments/list_utils.py +482 -0
- control_tower/environments/registry.py +275 -0
- control_tower/environments/remote/__init__.py +0 -0
- control_tower/environments/remote/process.py +248 -0
- control_tower/environments/remote/remote.py +407 -0
- control_tower/environments/remote/sample.py +244 -0
- control_tower/environments/setting_runtime.py +176 -0
- control_tower/environments/settings.py +1170 -0
- control_tower/environments/settings.yml +26 -0
- control_tower/environments/settings_cli.py +610 -0
- control_tower/environments/validate.py +116 -0
- control_tower/eval/README.md +38 -0
- control_tower/eval/__init__.py +1 -0
- control_tower/eval/game/__init__.py +11 -0
- control_tower/eval/game/blue.py +570 -0
- control_tower/eval/game/harness.py +146 -0
- control_tower/eval/game/history.py +221 -0
- control_tower/eval/game/messages.py +75 -0
- control_tower/eval/game/red.py +346 -0
- control_tower/eval/game/registry.py +29 -0
- control_tower/eval/game/solver.py +214 -0
- control_tower/eval/game/view.py +114 -0
- control_tower/eval/game/white.py +37 -0
- control_tower/eval/launch/__init__.py +1 -0
- control_tower/eval/launch/assemble.py +416 -0
- control_tower/eval/launch/dataset.py +611 -0
- control_tower/eval/launch/dispatch.py +142 -0
- control_tower/eval/launch/inspect_tasks.py +248 -0
- control_tower/eval/launch/metadata.py +39 -0
- control_tower/eval/launch/naming.py +37 -0
- control_tower/eval/launch/options.py +40 -0
- control_tower/eval/launch/preflight.py +90 -0
- control_tower/eval/launch/selection.py +151 -0
- control_tower/eval/log/README.md +76 -0
- control_tower/eval/log/__init__.py +0 -0
- control_tower/eval/log/action.py +72 -0
- control_tower/eval/log/blue_calls.py +93 -0
- control_tower/eval/log/convert.py +1773 -0
- control_tower/eval/log/eval_trace_metadata.py +15 -0
- control_tower/eval/log/extended_thinking.py +56 -0
- control_tower/eval/log/format_history.py +35 -0
- control_tower/eval/log/from_eval_log.py +141 -0
- control_tower/eval/log/merge.py +87 -0
- control_tower/eval/log/monitor_scores.py +112 -0
- control_tower/eval/log/sample.py +403 -0
- control_tower/eval/log/task_args.py +23 -0
- control_tower/eval/log/task_scores.py +110 -0
- control_tower/eval/log/tool_calls.py +40 -0
- control_tower/eval/log/translation.py +342 -0
- control_tower/eval/log/utils.py +85 -0
- control_tower/eval/log/workspace_diff.py +16 -0
- control_tower/eval/monitoring/__init__.py +1 -0
- control_tower/eval/monitoring/posthoc.py +339 -0
- control_tower/eval/monitoring/scorer.py +409 -0
- control_tower/extras/__init__.py +0 -0
- control_tower/extras/claude_host.py +26 -0
- control_tower/extras/dashboard.py +269 -0
- control_tower/extras/dashboard_cli.py +19 -0
- control_tower/extras/env_description/__init__.py +0 -0
- control_tower/extras/env_description/audit/README.md +80 -0
- control_tower/extras/env_description/audit/__init__.py +0 -0
- control_tower/extras/env_description/audit/cli.py +81 -0
- control_tower/extras/env_description/audit/models.py +63 -0
- control_tower/extras/env_description/audit/prompts/system.md +68 -0
- control_tower/extras/env_description/audit/prompts/user.md +30 -0
- control_tower/extras/env_description/audit/task.py +278 -0
- control_tower/extras/env_description/generate/README.md +75 -0
- control_tower/extras/env_description/generate/cli.py +146 -0
- control_tower/extras/env_description/generate/prompts/system.md +62 -0
- control_tower/extras/env_description/generate/prompts/user.md +21 -0
- control_tower/extras/env_description/generate/task.py +359 -0
- control_tower/extras/env_description/shared/concurrency.py +72 -0
- control_tower/extras/env_description/shared/spec.md +79 -0
- control_tower/extras/env_description/shared/spec_loader.py +24 -0
- control_tower/extras/env_source.py +230 -0
- control_tower/extras/integration_test_quality/README.md +59 -0
- control_tower/extras/integration_test_quality/cli.py +115 -0
- control_tower/extras/integration_test_quality/prompts/analyze.md +44 -0
- control_tower/extras/integration_test_quality/prompts/check_tasks.md +29 -0
- control_tower/extras/integration_test_quality/prompts/coverage.md +27 -0
- control_tower/extras/integration_test_quality/prompts/fix.md +14 -0
- control_tower/extras/integration_test_quality/task.py +179 -0
- control_tower/extras/pairing_analysis/README.md +30 -0
- control_tower/extras/pairing_analysis/cli.py +82 -0
- control_tower/extras/pairing_analysis/prompts/pairing.md +43 -0
- control_tower/extras/pairing_analysis/task.py +367 -0
- control_tower/extras/prompts.py +28 -0
- control_tower/extras/scorer_idempotency/base.py +216 -0
- control_tower/extras/scorer_idempotency/checks/_json.py +31 -0
- control_tower/extras/scorer_idempotency/checks/chroma.py +285 -0
- control_tower/extras/scorer_idempotency/checks/docker.py +366 -0
- control_tower/extras/scorer_idempotency/checks/elasticsearch.py +293 -0
- control_tower/extras/scorer_idempotency/checks/filesystem.py +155 -0
- control_tower/extras/scorer_idempotency/checks/generated_manifest.json +13 -0
- control_tower/extras/scorer_idempotency/checks/influxdb.py +549 -0
- control_tower/extras/scorer_idempotency/checks/listener.py +81 -0
- control_tower/extras/scorer_idempotency/checks/minio.py +360 -0
- control_tower/extras/scorer_idempotency/checks/mongo.py +338 -0
- control_tower/extras/scorer_idempotency/checks/openldap.py +241 -0
- control_tower/extras/scorer_idempotency/checks/postgres.py +317 -0
- control_tower/extras/scorer_idempotency/checks/process.py +134 -0
- control_tower/extras/scorer_idempotency/checks/rabbitmq.py +369 -0
- control_tower/extras/scorer_idempotency/checks/redis.py +291 -0
- control_tower/extras/scorer_idempotency/checks/sqlite.py +309 -0
- control_tower/extras/scorer_idempotency/checks/sshd.py +270 -0
- control_tower/extras/scorer_idempotency/checks/vault.py +376 -0
- control_tower/extras/scorer_idempotency/cli.py +575 -0
- control_tower/extras/scorer_idempotency/compose_analyzer.py +182 -0
- control_tower/extras/scorer_idempotency/detection.py +236 -0
- control_tower/extras/scorer_idempotency/dockerfile_analyzer.py +377 -0
- control_tower/extras/scorer_idempotency/llm_generator.py +448 -0
- control_tower/extras/scorer_idempotency/runner.py +449 -0
- control_tower/extras/scorer_idempotency/runtime_prober.py +378 -0
- control_tower/extras/side_task_folder.py +115 -0
- control_tower/extras/storage.py +74 -0
- control_tower/extras/task_analysis/compare_side_tasks/README.md +25 -0
- control_tower/extras/task_analysis/compare_side_tasks/cli.py +25 -0
- control_tower/extras/task_analysis/compare_side_tasks/task.py +127 -0
- control_tower/extras/task_analysis/doc_audit/README.md +77 -0
- control_tower/extras/task_analysis/doc_audit/audits/base.py +129 -0
- control_tower/extras/task_analysis/doc_audit/audits/goal.py +92 -0
- control_tower/extras/task_analysis/doc_audit/audits/sd.py +98 -0
- control_tower/extras/task_analysis/doc_audit/cli.py +185 -0
- control_tower/extras/task_analysis/doc_audit/heuristics/base.py +53 -0
- control_tower/extras/task_analysis/doc_audit/heuristics/goal.py +215 -0
- control_tower/extras/task_analysis/doc_audit/heuristics/sd.py +222 -0
- control_tower/extras/task_analysis/doc_audit/prompts/goal/system.md +109 -0
- control_tower/extras/task_analysis/doc_audit/prompts/goal/user.md +21 -0
- control_tower/extras/task_analysis/doc_audit/prompts/sd/system.md +311 -0
- control_tower/extras/task_analysis/doc_audit/prompts/sd/user.md +26 -0
- control_tower/extras/task_analysis/doc_audit/sd_audit_labels.json +98 -0
- control_tower/extras/task_analysis/doc_audit/task.py +200 -0
- control_tower/extras/task_analysis/failure_type_audit/cli.py +125 -0
- control_tower/extras/task_analysis/failure_type_audit/prompts/system.md +9 -0
- control_tower/extras/task_analysis/failure_type_audit/prompts/user.md +23 -0
- control_tower/extras/task_analysis/failure_type_audit/task.py +190 -0
- control_tower/extras/task_analysis/goal_completeness/README.md +56 -0
- control_tower/extras/task_analysis/goal_completeness/cli.py +125 -0
- control_tower/extras/task_analysis/goal_completeness/prompts/system.md +44 -0
- control_tower/extras/task_analysis/goal_completeness/prompts/user.md +50 -0
- control_tower/extras/task_analysis/goal_completeness/task.py +320 -0
- control_tower/extras/task_analysis/goal_completeness/viewer.py +142 -0
- control_tower/extras/task_analysis/reward_hacking/README.md +46 -0
- control_tower/extras/task_analysis/reward_hacking/cli.py +115 -0
- control_tower/extras/task_analysis/reward_hacking/prompts/system.md +71 -0
- control_tower/extras/task_analysis/reward_hacking/prompts/user.md +62 -0
- control_tower/extras/task_analysis/reward_hacking/task.py +360 -0
- control_tower/extras/task_analysis/reward_hacking/viewer.py +122 -0
- control_tower/extras/task_analysis/rt_alignment/README.md +45 -0
- control_tower/extras/task_analysis/rt_alignment/cli.py +119 -0
- control_tower/extras/task_analysis/rt_alignment/prompts/system.md +50 -0
- control_tower/extras/task_analysis/rt_alignment/prompts/user.md +57 -0
- control_tower/extras/task_analysis/rt_alignment/task.py +357 -0
- control_tower/extras/task_analysis/rt_alignment/viewer.py +124 -0
- control_tower/extras/task_analysis/side_task_review/README.md +43 -0
- control_tower/extras/task_analysis/side_task_review/cli.py +87 -0
- control_tower/extras/task_analysis/side_task_review/prompts/review.md +30 -0
- control_tower/extras/task_analysis/side_task_review/task.py +98 -0
- control_tower/extras/task_analysis/validate_cover/README.md +32 -0
- control_tower/extras/task_analysis/validate_cover/cli.py +97 -0
- control_tower/extras/task_analysis/validate_cover/container_script.py +230 -0
- control_tower/extras/task_analysis/validate_cover/full_env_context.py +66 -0
- control_tower/extras/task_analysis/validate_cover/task.py +97 -0
- control_tower/extras/task_analysis/validate_cover/validate_cover_prompt.md +35 -0
- control_tower/extras/timing_report/__init__.py +17 -0
- control_tower/extras/timing_report/cli.py +71 -0
- control_tower/extras/timing_report/report.py +324 -0
- control_tower/extras/traj_analysis/submit_guessability/README.md +49 -0
- control_tower/extras/traj_analysis/submit_guessability/cli.py +125 -0
- control_tower/extras/traj_analysis/submit_guessability/prompts/system.md +29 -0
- control_tower/extras/traj_analysis/submit_guessability/prompts/user.md +70 -0
- control_tower/extras/traj_analysis/submit_guessability/task.py +351 -0
- control_tower/extras/traj_analysis/submit_guessability/viewer.py +159 -0
- control_tower/extras/traj_analysis/submit_guessability_verified/README.md +52 -0
- control_tower/extras/traj_analysis/submit_guessability_verified/cli.py +143 -0
- control_tower/extras/traj_analysis/submit_guessability_verified/prompts/system.md +36 -0
- control_tower/extras/traj_analysis/submit_guessability_verified/prompts/user.md +61 -0
- control_tower/extras/traj_analysis/submit_guessability_verified/task.py +673 -0
- control_tower/extras/traj_analysis/submit_guessability_verified/viewer.py +186 -0
- control_tower/live/__init__.py +0 -0
- control_tower/live/agents.py +117 -0
- control_tower/live/assets/dev_container_extensions.json +3 -0
- control_tower/live/assets/live.gitignore +53 -0
- control_tower/live/assets/pyrightconfig.json +4 -0
- control_tower/live/assets/settings.json +3 -0
- control_tower/live/cli.py +1133 -0
- control_tower/live/compose.py +117 -0
- control_tower/live/context/print_context.py +98 -0
- control_tower/live/context/task_getters.py +65 -0
- control_tower/live/context/traj_dir.py +334 -0
- control_tower/live/integration_tests.py +128 -0
- control_tower/live/sandbox_ops.py +134 -0
- control_tower/live/score.py +85 -0
- control_tower/live/session.py +513 -0
- control_tower/live/setup.py +294 -0
- control_tower/live/task_state.py +145 -0
- control_tower/live/traj/__init__.py +0 -0
- control_tower/live/traj/gen_traj.py +319 -0
- control_tower/live/traj/git_status.py +33 -0
- control_tower/live/traj/git_traj.py +735 -0
- control_tower/live/traj/git_traj_helpers/FORMAT.md +25 -0
- control_tower/live/traj/git_traj_helpers/call_insert.py +15 -0
- control_tower/live/traj/git_traj_helpers/call_str_replace.py +20 -0
- control_tower/live/traj/macros.py +100 -0
- control_tower/live/traj/migrate_traj.py +80 -0
- control_tower/live/traj/patch.py +62 -0
- control_tower/live/traj/solution.py +242 -0
- control_tower/live/traj/templ/add.py +14 -0
- control_tower/live/traj/templ/add_tool_call.py +85 -0
- control_tower/live/traj/templ/bash.py +127 -0
- control_tower/live/traj/templ/git_utils.py +93 -0
- control_tower/live/traj/templ/input_utils.py +145 -0
- control_tower/live/traj/templ/template_from.py +118 -0
- control_tower/live/traj/templ/tool_call_builder.py +119 -0
- control_tower/live/traj/tool_call_utils.py +283 -0
- control_tower/live/traj/traj.py +294 -0
- control_tower/live/tui/__init__.py +0 -0
- control_tower/live/tui/popup.py +293 -0
- control_tower/live/tui/tui.py +1237 -0
- control_tower/live/tui/tui.tcss +32 -0
- control_tower/metrics/__init__.py +0 -0
- control_tower/metrics/audit.py +357 -0
- control_tower/metrics/control.py +120 -0
- control_tower/models/__init__.py +3 -0
- control_tower/models/aliases.py +59 -0
- control_tower/models/cache_scopes.py +26 -0
- control_tower/models/inspect_rates.py +32 -0
- control_tower/models/openrouter_provider.py +65 -0
- control_tower/models/pricing.py +183 -0
- control_tower/models/rates.py +240 -0
- control_tower/models/role_model.py +179 -0
- control_tower/models/usage_hooks.py +14 -0
- control_tower/monitors/__init__.py +40 -0
- control_tower/monitors/_monitor.py +722 -0
- control_tower/monitors/checklist.py +95 -0
- control_tower/monitors/jev.py +217 -0
- control_tower/monitors/jev_questions.yml +32 -0
- control_tower/monitors/presets.yml +29 -0
- control_tower/monitors/prompts/action-goals.md +78 -0
- control_tower/monitors/prompts/action.md +70 -0
- control_tower/monitors/prompts/goal-analysis.md +72 -0
- control_tower/monitors/prompts/goals.md +76 -0
- control_tower/monitors/prompts/no-history.md +37 -0
- control_tower/monitors/prompts/off-task.md +86 -0
- control_tower/monitors/prompts/quick-goals.md +65 -0
- control_tower/monitors/prompts/quick.md +57 -0
- control_tower/monitors/prompts/simple.md +19 -0
- control_tower/monitors/prompts/strong-goals.md +87 -0
- control_tower/monitors/prompts/strong.md +79 -0
- control_tower/monitors/prompts/trajectory.md +16 -0
- control_tower/monitors/registry.py +18 -0
- control_tower/monitors/single_goal.py +67 -0
- control_tower/monitors/steps/checklist.md +38 -0
- control_tower/monitors/steps/single_goal.md +75 -0
- control_tower/monitors/steps/single_goal_analysis.md +71 -0
- control_tower/monitors/steps/three_scores.md +107 -0
- control_tower/monitors/three_scores.py +68 -0
- control_tower/policies/__init__.py +1 -0
- control_tower/policies/attack.py +185 -0
- control_tower/policies/honest.py +23 -0
- control_tower/policies/obvious_attack.py +67 -0
- control_tower/policies/prompts/attack.md +7 -0
- control_tower/policies/prompts/attack_planning.md +22 -0
- control_tower/policies/replay.py +201 -0
- control_tower/policies/scripted.py +115 -0
- control_tower/protocols/__init__.py +1 -0
- control_tower/protocols/defer_to_trusted.py +141 -0
- control_tower/protocols/null_blue.py +27 -0
- control_tower/protocols/prompts/trusted_editing.md +9 -0
- control_tower/protocols/resample.py +191 -0
- control_tower/protocols/trusted_editing.py +143 -0
- control_tower/protocols/trusted_monitoring.py +90 -0
- control_tower/protocols/watcher.py +352 -0
- control_tower/py.typed +0 -0
- control_tower/runs/README.md +93 -0
- control_tower/runs/__init__.py +0 -0
- control_tower/runs/convert.py +100 -0
- control_tower/runs/convert_eval_log_to_run.py +92 -0
- control_tower/runs/eval_log_io.py +200 -0
- control_tower/runs/io.py +190 -0
- control_tower/runs/models.py +64 -0
- control_tower/runs/mongo_docs.py +359 -0
- control_tower/runs/runs_cli.py +735 -0
- control_tower/runs/s3_upload.py +167 -0
- control_tower/runs/source.py +24 -0
- control_tower/runs/timing_utils.py +149 -0
- control_tower/runs/viewer/__init__.py +0 -0
- control_tower/runs/viewer/api.py +464 -0
- control_tower/runs/viewer/auth.py +50 -0
- control_tower/runs/viewer/io.py +204 -0
- control_tower/runs/viewer/models.py +25 -0
- control_tower/runs/viewer/upload.py +216 -0
- control_tower/runs/viewer/urls.py +501 -0
- control_tower/sandbox/__init__.py +6 -0
- control_tower/sandbox/_alpine_rootfs.py +230 -0
- control_tower/sandbox/_ssh.py +471 -0
- control_tower/sandbox/cli.py +236 -0
- control_tower/sandbox/constants.py +21 -0
- control_tower/sandbox/docker.py +685 -0
- control_tower/sandbox/docker_in_vm.py +527 -0
- control_tower/sandbox/firecracker.py +363 -0
- control_tower/sandbox/inspect_tools.py +46 -0
- control_tower/sandbox/qemu.py +251 -0
- control_tower/sandbox/replay/__init__.py +23 -0
- control_tower/sandbox/replay/environment.py +435 -0
- control_tower/sandbox/replay/score.py +37 -0
- control_tower/sandbox/simulated/__init__.py +25 -0
- control_tower/sandbox/simulated/actions.py +90 -0
- control_tower/sandbox/simulated/compose.py +192 -0
- control_tower/sandbox/simulated/environment.py +190 -0
- control_tower/sandbox/simulated/judge.py +158 -0
- control_tower/sandbox/simulated/prompts/judge.md +31 -0
- control_tower/sandbox/simulated/prompts/simulator.md +74 -0
- control_tower/sandbox/simulated/prompts/simulator_no_exec.md +22 -0
- control_tower/sandbox/simulated/scratch/Dockerfile +11 -0
- control_tower/sandbox/simulated/scratch/compose.yaml +42 -0
- control_tower/sandbox/simulated/session.py +155 -0
- control_tower/scanners.py +130 -0
- control_tower/task/__init__.py +0 -0
- control_tower/task/final_diff.py +88 -0
- control_tower/task/sample_metadata.py +46 -0
- control_tower/task/scorers.py +327 -0
- control_tower/task/select_utils.py +106 -0
- control_tower/task/selectors.py +49 -0
- control_tower/task/setup.py +115 -0
- control_tower/task/space.py +45 -0
- control_tower/task/task.py +230 -0
- control_tower/task/task_sets/__init__.py +203 -0
- control_tower/task/traj_dir.py +39 -0
- control_tower/task/workspace_baseline.py +581 -0
- control_tower/tools/__init__.py +0 -0
- control_tower/tools/answer.py +45 -0
- control_tower/tools/bash_tools.py +37 -0
- control_tower/tools/declarations.py +23 -0
- control_tower/tools/diff_display_text_editor.py +66 -0
- control_tower/tools/registry.py +34 -0
- control_tower/tools/split_text_editor_tools.py +125 -0
- control_tower/tools/task_tools.py +204 -0
- control_tower/tools/text_editor_output.py +53 -0
- control_tower/tools/tool_calls.py +44 -0
- control_tower/tools/tool_registry.py +363 -0
- control_tower/utils/__init__.py +0 -0
- control_tower/utils/cli_args.py +20 -0
- control_tower/utils/direct_api_generator.py +124 -0
- control_tower/utils/docs_utils.py +18 -0
- control_tower/utils/env.py +191 -0
- control_tower/utils/file_patterns.py +89 -0
- control_tower/utils/fs.py +137 -0
- control_tower/utils/function_registry/__init__.py +0 -0
- control_tower/utils/function_registry/base_model.py +30 -0
- control_tower/utils/function_registry/registry.py +535 -0
- control_tower/utils/function_registry/selection.py +68 -0
- control_tower/utils/paths.py +91 -0
- control_tower/utils/sanitize.py +29 -0
- control_tower/utils/suggestions.py +25 -0
- control_tower/utils/time.py +8 -0
- control_tower/utils/transcript.py +20 -0
- control_tower/utils/versioning.py +327 -0
- control_tower-6.0.0.dist-info/METADATA +91 -0
- control_tower-6.0.0.dist-info/RECORD +446 -0
- control_tower-6.0.0.dist-info/WHEEL +5 -0
- control_tower-6.0.0.dist-info/entry_points.txt +5 -0
- control_tower-6.0.0.dist-info/licenses/LICENSE +21 -0
- control_tower-6.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Where an eval runs: in this process, on the EC2 fleet, or on Hawk."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from inspect_ai.log import EvalLog
|
|
8
|
+
|
|
9
|
+
from control_tower.eval.launch.options import EvalCliInputs
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Backend(ABC):
|
|
13
|
+
"""Where `run_task` runs an eval, given its completed task args."""
|
|
14
|
+
|
|
15
|
+
def resolvable(self, name: str) -> bool:
|
|
16
|
+
"""Whether the task set, policy, protocol or monitor ``name`` is looked up before the run; the rest are looked
|
|
17
|
+
up where the task is built."""
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def run(
|
|
22
|
+
self,
|
|
23
|
+
task_name: str,
|
|
24
|
+
task_args: dict[str, Any],
|
|
25
|
+
eval_inputs: "EvalCliInputs",
|
|
26
|
+
*,
|
|
27
|
+
upload: bool,
|
|
28
|
+
metadata: dict[str, Any],
|
|
29
|
+
) -> "EvalLog | None":
|
|
30
|
+
"""Run ``task_name`` with ``task_args``, ``metadata`` joining its eval metadata, and upload the log when
|
|
31
|
+
``upload``; return it, or None when it stays where it ran."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Fleet mode: distributed evaluation on EC2 workers."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Worker AMI resolution for fleet mode: the pre-baked AMI named in SSM ``/linuxbench/worker-ami-id``."""
|
|
2
|
+
|
|
3
|
+
import boto3
|
|
4
|
+
import botocore.exceptions
|
|
5
|
+
|
|
6
|
+
SSM_AMI_PARAM = "/linuxbench/worker-ami-id"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_worker_ami_id(region: str = "us-east-1", requested: str | None = None) -> str:
|
|
10
|
+
"""Return the AMI to use for fleet workers, refusing one that cannot boot now.
|
|
11
|
+
|
|
12
|
+
``requested`` is an AMI id, or the SSM parameter naming one; the default is ``SSM_AMI_PARAM``.
|
|
13
|
+
"""
|
|
14
|
+
source = requested or SSM_AMI_PARAM
|
|
15
|
+
ami_id = source
|
|
16
|
+
if source.startswith("/"):
|
|
17
|
+
ssm = boto3.client("ssm", region_name=region)
|
|
18
|
+
try:
|
|
19
|
+
ami_id = ssm.get_parameter(Name=source)["Parameter"]["Value"]
|
|
20
|
+
except ssm.exceptions.ParameterNotFound as e:
|
|
21
|
+
raise RuntimeError(
|
|
22
|
+
f"No worker AMI: SSM parameter {source} does not exist in {region}. "
|
|
23
|
+
f"Build one with scripts/build_worker_ami.sh --region {region}."
|
|
24
|
+
) from e
|
|
25
|
+
label = ami_id if ami_id == source else f"{ami_id} (from {source})"
|
|
26
|
+
ec2 = boto3.client("ec2", region_name=region)
|
|
27
|
+
try:
|
|
28
|
+
images = ec2.describe_images(ImageIds=[ami_id])["Images"]
|
|
29
|
+
except botocore.exceptions.ClientError as e:
|
|
30
|
+
if not e.response["Error"]["Code"].startswith("InvalidAMIID"):
|
|
31
|
+
raise
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
f"Worker AMI {label} not found in {region}: "
|
|
34
|
+
f"{e.response['Error']['Message']}"
|
|
35
|
+
) from e
|
|
36
|
+
if not images:
|
|
37
|
+
raise RuntimeError(f"Worker AMI {label} not found in {region}.")
|
|
38
|
+
image = images[0]
|
|
39
|
+
if image["State"] != "available":
|
|
40
|
+
reason = image.get("StateReason", {}).get("Message", "")
|
|
41
|
+
raise RuntimeError(
|
|
42
|
+
f"Worker AMI {label} in {region} is {image['State']}"
|
|
43
|
+
f"{f' ({reason})' if reason else ''}, not available; no workers launched."
|
|
44
|
+
)
|
|
45
|
+
return ami_id
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""AWS caller identity lookup for resource tagging."""
|
|
2
|
+
|
|
3
|
+
import boto3
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def caller_identity_name(region: str) -> str:
|
|
7
|
+
"""Get the caller's identity name (IAM username or SSO session name) for resource tagging."""
|
|
8
|
+
sts = boto3.client("sts", region_name=region)
|
|
9
|
+
return sts.get_caller_identity()["Arn"].rsplit("/", 1)[-1]
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Fleet worker bootstrap script — runs as EC2 user-data (root).
|
|
3
|
+
#
|
|
4
|
+
# Template variables (replaced by controller at launch time):
|
|
5
|
+
# {s3_bucket} — S3 bucket for fleet results and code artifact
|
|
6
|
+
# {s3_prefix} — S3 prefix for this fleet run (fleet_id/)
|
|
7
|
+
# {code_s3_key} — S3 key of the code tarball
|
|
8
|
+
# {rates_s3_key} — S3 key of the driver's rate table (empty when pricing is off)
|
|
9
|
+
# {worker_setup_s3_key} — S3 key of the run's worker setup script (empty when none)
|
|
10
|
+
# {region} — AWS region
|
|
11
|
+
# {work_queue_url} — SQS work queue URL
|
|
12
|
+
# {done_queue_url} — SQS done queue URL
|
|
13
|
+
# {concurrency} — Concurrent eval tasks per worker
|
|
14
|
+
# {idle_timeout} — Seconds idle before worker self-terminates
|
|
15
|
+
# {fleet_secrets_prefix} — SSM path holding this fleet's per-instance
|
|
16
|
+
# SecureStrings (JSON of API keys, GH_TOKEN, Docker
|
|
17
|
+
# auth, env-required vars), one per instance id
|
|
18
|
+
# {setting_archives} — space-separated `<s3 key>=<dir>` pairs: the controller's
|
|
19
|
+
# setting checkouts, one archive of each repo's files and
|
|
20
|
+
# one of its shallow .git, each extracted into
|
|
21
|
+
# /opt/control-tower/<dir>
|
|
22
|
+
# {env_secret_packages} — space-separated setting packages whose `<setting>.env` text
|
|
23
|
+
# sits in "<instance secrets param>.env.<package>"
|
|
24
|
+
# {docker_org_name_ssm_param} — shared SSM param for Docker org name
|
|
25
|
+
# {docker_org_pat_ssm_param} — shared SSM param for Docker PAT
|
|
26
|
+
#
|
|
27
|
+
# What the pre-baked AMI provides (see scripts/build_worker_ami.sh):
|
|
28
|
+
# - Docker + compose plugin, with the address pool below already in daemon.json
|
|
29
|
+
# - NVIDIA driver + container toolkit, which a GPU worker needs to pass its GPU check
|
|
30
|
+
# - uv installed for both ubuntu and root users
|
|
31
|
+
# - Pre-pulled Docker images for all environments and shared services
|
|
32
|
+
# - Python wheel cache at /opt/uv-cache
|
|
33
|
+
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
export HOME=/root
|
|
36
|
+
exec > /var/log/lb-worker-bootstrap.log 2>&1
|
|
37
|
+
|
|
38
|
+
# Upload bootstrap log to S3 on exit (success or failure) for debugging
|
|
39
|
+
IMDS_TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" || true)
|
|
40
|
+
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" http://169.254.169.254/latest/meta-data/instance-id || echo "unknown")
|
|
41
|
+
on_exit() {{
|
|
42
|
+
local rc=$?
|
|
43
|
+
aws s3 cp /var/log/lb-worker-bootstrap.log \
|
|
44
|
+
's3://{s3_bucket}/{s3_prefix}logs/'$INSTANCE_ID'-bootstrap.log' \
|
|
45
|
+
--region {region} || true
|
|
46
|
+
if [ "$rc" -ne 0 ]; then
|
|
47
|
+
detail=$(tail -n 5 /var/log/lb-worker-bootstrap.log | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
|
|
48
|
+
aws sqs send-message --queue-url '{done_queue_url}' --region {region} \
|
|
49
|
+
--message-body '{{"type": "bootstrap_failed", "instance_id": "'"$INSTANCE_ID"'", "detail": '"$detail"'}}' || true
|
|
50
|
+
shutdown -h now
|
|
51
|
+
fi
|
|
52
|
+
}}
|
|
53
|
+
trap on_exit EXIT
|
|
54
|
+
|
|
55
|
+
SECONDS=0
|
|
56
|
+
echo "=== Control Tower Fleet Worker Bootstrap ==="
|
|
57
|
+
echo "Instance: $INSTANCE_ID"
|
|
58
|
+
echo "Started at $(date)"
|
|
59
|
+
|
|
60
|
+
resolve_ssm_param() {{
|
|
61
|
+
local param_name="$1"
|
|
62
|
+
local with_decryption="${{2:-false}}"
|
|
63
|
+
if [ -z "$param_name" ]; then
|
|
64
|
+
return 0
|
|
65
|
+
fi
|
|
66
|
+
if [ "$with_decryption" = "true" ]; then
|
|
67
|
+
aws ssm get-parameter \
|
|
68
|
+
--name "$param_name" \
|
|
69
|
+
--with-decryption \
|
|
70
|
+
--query 'Parameter.Value' \
|
|
71
|
+
--output text \
|
|
72
|
+
--region {region} || true
|
|
73
|
+
else
|
|
74
|
+
aws ssm get-parameter \
|
|
75
|
+
--name "$param_name" \
|
|
76
|
+
--query 'Parameter.Value' \
|
|
77
|
+
--output text \
|
|
78
|
+
--region {region} || true
|
|
79
|
+
fi
|
|
80
|
+
}}
|
|
81
|
+
|
|
82
|
+
configure_docker_auth() {{
|
|
83
|
+
local org_name="${{DOCKERHUB_USERNAME:-}}"
|
|
84
|
+
local org_pat="${{DOCKERHUB_TOKEN:-}}"
|
|
85
|
+
local org_name_param='{docker_org_name_ssm_param}'
|
|
86
|
+
local org_pat_param='{docker_org_pat_ssm_param}'
|
|
87
|
+
|
|
88
|
+
if [ -z "$org_name" ]; then
|
|
89
|
+
org_name=$(resolve_ssm_param "$org_name_param")
|
|
90
|
+
fi
|
|
91
|
+
if [ -z "$org_pat" ]; then
|
|
92
|
+
org_pat=$(resolve_ssm_param "$org_pat_param" true)
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if [ -z "$org_name" ] && [ -z "$org_pat" ]; then
|
|
96
|
+
echo "FATAL: No Docker registry auth configured (need DOCKERHUB_USERNAME/DOCKERHUB_TOKEN in the launching shell or the shared SSM params)"
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
if [ -z "$org_name" ] || [ -z "$org_pat" ]; then
|
|
100
|
+
echo "FATAL: Incomplete Docker registry auth configuration"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if ! printf '%s\n' "$org_pat" | docker login -u "$org_name" --password-stdin >/dev/null; then
|
|
105
|
+
echo "FATAL: Docker registry login failed for $org_name"
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
unset org_pat
|
|
109
|
+
echo " [+${{SECONDS}}s] Docker registry login configured for $org_name"
|
|
110
|
+
}}
|
|
111
|
+
|
|
112
|
+
# --- inotify limit ------------------------------------------------------
|
|
113
|
+
# The kernel's 128 inotify instances per user are shared by every container; file-watching dev servers exhaust them at fleet concurrency.
|
|
114
|
+
sysctl -w fs.inotify.max_user_instances=8192 >/dev/null
|
|
115
|
+
echo " [+${{SECONDS}}s] inotify max_user_instances=$(cat /proc/sys/fs/inotify/max_user_instances)"
|
|
116
|
+
|
|
117
|
+
# --- Docker daemon config: the AMI's address pool -----------------------
|
|
118
|
+
# Docker's ~30-network default pool exhausts at concurrency; 10.224/11 as /24s gives 8192 networks clear of the VPC's 172.31/16.
|
|
119
|
+
if ! python3 -c '
|
|
120
|
+
import json, sys
|
|
121
|
+
config = json.load(open("/etc/docker/daemon.json"))
|
|
122
|
+
sys.exit(config.get("default-address-pools") != [{{"base": "10.224.0.0/11", "size": 24}}])
|
|
123
|
+
'; then
|
|
124
|
+
echo "FATAL: /etc/docker/daemon.json lacks the 10.224.0.0/11 /24 default address pool. Fleet workers boot only from the worker AMI built by scripts/build_worker_ami.sh, which bakes it."
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# --- Docker -----------------------------------------------------------
|
|
130
|
+
docker version --format json >/dev/null 2>&1 || {{ echo "FATAL: Docker is not running. Fleet workers boot only from the worker AMI built by scripts/build_worker_ami.sh."; exit 1; }}
|
|
131
|
+
echo " [+${{SECONDS}}s] Docker ready"
|
|
132
|
+
|
|
133
|
+
# --- Secrets (per-instance SSM SecureString; user-data carries the path only) ---
|
|
134
|
+
# The controller writes this parameter right after RunInstances and tags it
|
|
135
|
+
# with this instance's ARN; the instance role reads only parameters tagged
|
|
136
|
+
# with its own ARN, so the fetch is retried until the write and tag land.
|
|
137
|
+
if [ "$INSTANCE_ID" = unknown ]; then
|
|
138
|
+
echo "FATAL: IMDS did not return an instance id, so this instance's secrets parameter cannot be located"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
FLEET_SECRETS_PARAM='{fleet_secrets_prefix}'/"$INSTANCE_ID"
|
|
142
|
+
SECRETS_DEADLINE=$((SECONDS + 900))
|
|
143
|
+
SECRETS_NEXT_LOG=$SECONDS
|
|
144
|
+
until secrets=$(AWS_RETRY_MODE=standard AWS_MAX_ATTEMPTS=10 aws ssm get-parameter \
|
|
145
|
+
--name "$FLEET_SECRETS_PARAM" --with-decryption \
|
|
146
|
+
--query 'Parameter.Value' --output text --region {region} 2>/tmp/secrets-fetch.err); do
|
|
147
|
+
if [ "$SECONDS" -ge "$SECRETS_DEADLINE" ]; then
|
|
148
|
+
echo "FATAL: cannot read $FLEET_SECRETS_PARAM after 15 minutes: $(cat /tmp/secrets-fetch.err)"
|
|
149
|
+
echo " (does the instance role allow ssm:GetParameter where the InstanceArn tag is this instance, and did the controller tag the parameter?)"
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
if [ "$SECONDS" -ge "$SECRETS_NEXT_LOG" ]; then
|
|
153
|
+
echo " [+${{SECONDS}}s] waiting for $FLEET_SECRETS_PARAM: $(tail -n 1 /tmp/secrets-fetch.err)"
|
|
154
|
+
SECRETS_NEXT_LOG=$((SECONDS + 60))
|
|
155
|
+
fi
|
|
156
|
+
sleep 5
|
|
157
|
+
done
|
|
158
|
+
exports=$(printf '%s' "$secrets" | python3 -c '
|
|
159
|
+
import json, shlex, sys
|
|
160
|
+
for name, value in json.load(sys.stdin).items():
|
|
161
|
+
print("export " + name + "=" + shlex.quote(value))
|
|
162
|
+
')
|
|
163
|
+
eval "$exports"
|
|
164
|
+
unset secrets exports
|
|
165
|
+
echo " [+${{SECONDS}}s] Secrets fetched from SSM"
|
|
166
|
+
|
|
167
|
+
# --- Docker registry auth (early — fail fast before expensive steps) ----
|
|
168
|
+
configure_docker_auth
|
|
169
|
+
unset DOCKERHUB_TOKEN DOCKERHUB_USERNAME
|
|
170
|
+
|
|
171
|
+
PCI_VENDORS=$(cat /sys/bus/pci/devices/*/vendor)
|
|
172
|
+
if grep -qx 0x10de <<< "$PCI_VENDORS"; then
|
|
173
|
+
docker run --rm --gpus all ubuntu:22.04 nvidia-smi -L
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
export PATH="/root/.local/bin:$PATH"
|
|
177
|
+
|
|
178
|
+
# --- Code download ----------------------------------------------------
|
|
179
|
+
rm -rf /opt/control-tower && mkdir -p /opt/control-tower
|
|
180
|
+
cd /opt/control-tower
|
|
181
|
+
aws s3 cp 's3://{s3_bucket}/{code_s3_key}' code.tar.gz --region {region}
|
|
182
|
+
tar xzf code.tar.gz
|
|
183
|
+
rm code.tar.gz
|
|
184
|
+
echo " [+${{SECONDS}}s] Code downloaded and extracted"
|
|
185
|
+
|
|
186
|
+
# --- SSH known hosts (needed for git+ssh deps in uv sync) ----------------
|
|
187
|
+
mkdir -p ~/.ssh
|
|
188
|
+
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
|
|
189
|
+
chmod 600 ~/.ssh/known_hosts
|
|
190
|
+
echo " [+${{SECONDS}}s] GitHub SSH host key added to known_hosts"
|
|
191
|
+
|
|
192
|
+
# --- GitHub credentials (needed BEFORE uv sync for private git+https deps) ---
|
|
193
|
+
# uv shells out to plain git, which ignores GH_TOKEN — only gh reads it
|
|
194
|
+
# natively. A credential helper scoped to https://github.com supplies it
|
|
195
|
+
# when git asks; deliberately NOT a url.insteadOf rewrite, which would
|
|
196
|
+
# mutate every GitHub URL on the worker.
|
|
197
|
+
git config --global credential.'https://github.com'.helper \
|
|
198
|
+
'!f() {{ echo "username=x-access-token"; echo "password=${{GH_TOKEN}}"; }}; f'
|
|
199
|
+
echo " [+${{SECONDS}}s] GitHub HTTPS credential helper configured"
|
|
200
|
+
|
|
201
|
+
# --- Python deps -------------------------------------------------------
|
|
202
|
+
export UV_CACHE_DIR=/opt/uv-cache
|
|
203
|
+
export UV_PYTHON_INSTALL_DIR=/opt/uv-python
|
|
204
|
+
# git archive strips .git, so setuptools-scm can't detect version. Fake it.
|
|
205
|
+
export SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0
|
|
206
|
+
if [ -f uv.lock ]; then
|
|
207
|
+
uv sync --frozen
|
|
208
|
+
else
|
|
209
|
+
echo " No uv.lock in code artifact; resolving dependencies from pyproject.toml"
|
|
210
|
+
uv sync
|
|
211
|
+
fi
|
|
212
|
+
echo " [+${{SECONDS}}s] Python deps installed"
|
|
213
|
+
# Defeat AMI shadowing: a pre-baked control_tower elsewhere in the worker
|
|
214
|
+
# Python's sys.path can win priority over the bundle's editable install,
|
|
215
|
+
# so the worker silently runs OLDER code even though uv sync reports success.
|
|
216
|
+
# (Verified via sentinel string round-trip: stale install wins by default.)
|
|
217
|
+
# Three defense layers:
|
|
218
|
+
# 1. Re-install editable into the venv explicitly.
|
|
219
|
+
# 2. Purge any other control_tower sitting in the venv's site-packages.
|
|
220
|
+
# 3. Prepend /opt/control-tower/src to PYTHONPATH so the bundle is the FIRST
|
|
221
|
+
# sys.path entry no matter what.
|
|
222
|
+
uv pip install --no-deps -e .
|
|
223
|
+
VENV_SITE=$(uv run python -c "import sysconfig; print(sysconfig.get_paths()['purelib'])")
|
|
224
|
+
echo " [+${{SECONDS}}s] venv site-packages = $VENV_SITE"
|
|
225
|
+
# Remove any non-editable control_tower copy — but only when the bundle
|
|
226
|
+
# ships its own (out-of-tree consumers rely on the pip-installed one).
|
|
227
|
+
if [ -d /opt/control-tower/src/control_tower ] && [ -d "$VENV_SITE/control_tower" ] && [ ! -L "$VENV_SITE/control_tower" ]; then
|
|
228
|
+
echo " [+${{SECONDS}}s] Removing non-editable control_tower from $VENV_SITE"
|
|
229
|
+
rm -rf "$VENV_SITE/control_tower"
|
|
230
|
+
fi
|
|
231
|
+
# On PYTHONPATH, src/control_tower.egg-info shadows the venv's dist-info, and Inspect then registers tasks, scorers and metrics without the control_tower/ prefix.
|
|
232
|
+
rm -rf /opt/control-tower/src/control_tower.egg-info
|
|
233
|
+
export PYTHONPATH="/opt/control-tower/src${{PYTHONPATH:+:$PYTHONPATH}}"
|
|
234
|
+
echo " [+${{SECONDS}}s] PYTHONPATH=$PYTHONPATH"
|
|
235
|
+
# Verify resolution
|
|
236
|
+
RESOLVED=$(uv run python -c "import control_tower; print(control_tower.__file__)")
|
|
237
|
+
echo " [+${{SECONDS}}s] control_tower resolves to: $RESOLVED"
|
|
238
|
+
|
|
239
|
+
export CT_FLEET_WORKER_USER='{worker_user}'
|
|
240
|
+
if [ "$CT_FLEET_WORKER_USER" != root ]; then
|
|
241
|
+
worker_home="$(getent passwd "$CT_FLEET_WORKER_USER" | cut -d: -f6)"
|
|
242
|
+
install -d -o "$CT_FLEET_WORKER_USER" -m 700 "$worker_home/.docker"
|
|
243
|
+
install -o "$CT_FLEET_WORKER_USER" -m 600 /root/.docker/config.json "$worker_home/.docker/config.json"
|
|
244
|
+
usermod -aG docker "$CT_FLEET_WORKER_USER"
|
|
245
|
+
runuser -u "$CT_FLEET_WORKER_USER" -- env HOME="$worker_home" docker info >/dev/null || {{ echo "FATAL: $CT_FLEET_WORKER_USER cannot reach the Docker daemon"; exit 1; }}
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
# --- Settings: the controller's checkouts of each shipped repo ---
|
|
249
|
+
SETTING_ARCHIVES='{setting_archives}'
|
|
250
|
+
for archive in $SETTING_ARCHIVES; do
|
|
251
|
+
mkdir -p "/opt/control-tower/${{archive#*=}}"
|
|
252
|
+
aws s3 cp "s3://{s3_bucket}/${{archive%%=*}}" - --region {region} \
|
|
253
|
+
| tar xz -C "/opt/control-tower/${{archive#*=}}"
|
|
254
|
+
done
|
|
255
|
+
echo " [+${{SECONDS}}s] Settings extracted: $(ls .settings 2>/dev/null | tr '\n' ' ')"
|
|
256
|
+
|
|
257
|
+
# --- Setting values: each shipped setting's `<setting>.env`, beside its checkout as on the controller ---
|
|
258
|
+
ENV_SECRET_PACKAGES='{env_secret_packages}'
|
|
259
|
+
for package in $ENV_SECRET_PACKAGES; do
|
|
260
|
+
attempt=0
|
|
261
|
+
until package_values=$(AWS_RETRY_MODE=standard AWS_MAX_ATTEMPTS=10 aws ssm get-parameter \
|
|
262
|
+
--name "$FLEET_SECRETS_PARAM.env.$package" --with-decryption \
|
|
263
|
+
--query 'Parameter.Value' --output text --region {region} 2>/tmp/secrets-fetch.err); do
|
|
264
|
+
attempt=$((attempt + 1))
|
|
265
|
+
if [ "$attempt" -ge 5 ]; then
|
|
266
|
+
echo "FATAL: cannot read $FLEET_SECRETS_PARAM.env.$package: $(cat /tmp/secrets-fetch.err)"
|
|
267
|
+
exit 1
|
|
268
|
+
fi
|
|
269
|
+
sleep $((attempt * 5))
|
|
270
|
+
done
|
|
271
|
+
install -o "$CT_FLEET_WORKER_USER" -m 600 /dev/null ".settings/$package.env"
|
|
272
|
+
printf '%s\n' "$package_values" > ".settings/$package.env"
|
|
273
|
+
done
|
|
274
|
+
unset package_values
|
|
275
|
+
|
|
276
|
+
# --- Setting package venvs: the env processes CT spawns run from them ---
|
|
277
|
+
for package_dir in .settings/*/; do
|
|
278
|
+
[ -f "$package_dir/pyproject.toml" ] || continue
|
|
279
|
+
sync_flags=()
|
|
280
|
+
[ -f "$package_dir/uv.lock" ] && sync_flags=(--frozen)
|
|
281
|
+
if ! uv sync --project "$package_dir" --no-dev "${{sync_flags[@]}}" 2>&1; then
|
|
282
|
+
echo " [+${{SECONDS}}s] FATAL: uv sync failed for setting package $package_dir."
|
|
283
|
+
echo " Workers cannot start its env process without its venv; check the"
|
|
284
|
+
echo " package's pyproject.toml and uv.lock as shipped."
|
|
285
|
+
exit 1
|
|
286
|
+
fi
|
|
287
|
+
echo " [+${{SECONDS}}s] Setting package venv ready: $package_dir"
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
# --- Setting plugins: each shipped setting's ct_plugin/ joins CT's venv, as `ct settings pull` does ---
|
|
291
|
+
for plugin_dir in .settings/*/ct_plugin/; do
|
|
292
|
+
[ -f "$plugin_dir/pyproject.toml" ] || continue
|
|
293
|
+
if ! uv pip install --python .venv/bin/python -e "$plugin_dir" 2>&1; then
|
|
294
|
+
echo " [+${{SECONDS}}s] FATAL: installing setting plugin $plugin_dir failed."
|
|
295
|
+
exit 1
|
|
296
|
+
fi
|
|
297
|
+
echo " [+${{SECONDS}}s] Setting plugin installed: $plugin_dir"
|
|
298
|
+
done
|
|
299
|
+
|
|
300
|
+
# --- Worker setup (--ec2-arg worker_setup): e.g. an external sandbox's host runtime ---
|
|
301
|
+
# It sees the venv at /opt/control-tower/.venv; /opt/control-tower-worker-setup.env is sourced after it.
|
|
302
|
+
WORKER_SETUP_S3_KEY='{worker_setup_s3_key}'
|
|
303
|
+
if [ -n "$WORKER_SETUP_S3_KEY" ]; then
|
|
304
|
+
aws s3 cp "s3://{s3_bucket}/$WORKER_SETUP_S3_KEY" /opt/control-tower-worker-setup.sh --region {region}
|
|
305
|
+
echo " [+${{SECONDS}}s] Running worker setup..."
|
|
306
|
+
if ! bash -e /opt/control-tower-worker-setup.sh; then
|
|
307
|
+
echo " [+${{SECONDS}}s] FATAL: worker setup failed"
|
|
308
|
+
exit 1
|
|
309
|
+
fi
|
|
310
|
+
if [ -f /opt/control-tower-worker-setup.env ]; then
|
|
311
|
+
# shellcheck disable=SC1091
|
|
312
|
+
source /opt/control-tower-worker-setup.env
|
|
313
|
+
fi
|
|
314
|
+
echo " [+${{SECONDS}}s] Worker setup finished"
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# --- Model rates: the driver's table, so workers price as the driver does ---
|
|
319
|
+
RATES_S3_KEY='{rates_s3_key}'
|
|
320
|
+
if [ -n "$RATES_S3_KEY" ]; then
|
|
321
|
+
aws s3 cp "s3://{s3_bucket}/$RATES_S3_KEY" /opt/control-tower/model_rates.json --region {region}
|
|
322
|
+
export CONTROL_TOWER_MODEL_RATES=/opt/control-tower/model_rates.json
|
|
323
|
+
else
|
|
324
|
+
export CONTROL_TOWER_PRICING=off
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
# --- Worker -------------------------------------------------------------
|
|
328
|
+
echo "=== Starting worker at +${{SECONDS}}s ==="
|
|
329
|
+
export CONTROL_TOWER_IMAGES=pull
|
|
330
|
+
export INSPECT_LOG_LEVEL='{inspect_log_level}'
|
|
331
|
+
if [ "$CT_FLEET_WORKER_USER" != root ]; then
|
|
332
|
+
mkdir -p /opt/uv-python
|
|
333
|
+
chown -R "$CT_FLEET_WORKER_USER": /opt/control-tower /opt/uv-cache /opt/uv-python
|
|
334
|
+
if [ ! "$(command -v uv)" -ef /usr/local/bin/uv ]; then
|
|
335
|
+
install -m 755 "$(command -v uv)" /usr/local/bin/uv
|
|
336
|
+
fi
|
|
337
|
+
echo "$CT_FLEET_WORKER_USER ALL=(ALL) NOPASSWD: /usr/sbin/shutdown" > /etc/sudoers.d/fleet-worker
|
|
338
|
+
chmod 440 /etc/sudoers.d/fleet-worker
|
|
339
|
+
fi
|
|
340
|
+
runuser -u "$CT_FLEET_WORKER_USER" -- env HOME="$(getent passwd "$CT_FLEET_WORKER_USER" | cut -d: -f6)" PATH="$PATH" \
|
|
341
|
+
uv run python -m control_tower.backends.fleet.worker_cli \
|
|
342
|
+
--queue-url '{work_queue_url}' \
|
|
343
|
+
--done-queue-url '{done_queue_url}' \
|
|
344
|
+
--s3-bucket '{s3_bucket}' \
|
|
345
|
+
--concurrency {concurrency} \
|
|
346
|
+
--idle-timeout {idle_timeout} \
|
|
347
|
+
--region {region}
|
|
348
|
+
|
|
349
|
+
echo "=== Worker finished at $(date) ==="
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Fleet run options."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Ec2Config(BaseModel):
|
|
9
|
+
"""The fleet ``--ec2`` launches.
|
|
10
|
+
|
|
11
|
+
Every field is settable as ``--ec2-arg key=value``, so a new knob is a field here and never a
|
|
12
|
+
new CLI flag.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
|
16
|
+
|
|
17
|
+
instance_type: str = "c7i.4xlarge"
|
|
18
|
+
instance_type_fallbacks: list[str] = []
|
|
19
|
+
region: str = "us-east-1"
|
|
20
|
+
max_workers: int = 24
|
|
21
|
+
# Workers start at 10 and adapt within [1, max] from observed rate-limit pressure.
|
|
22
|
+
concurrency_per_worker: int = Field(default=32, ge=1)
|
|
23
|
+
# Print the cost estimate and exit without launching.
|
|
24
|
+
estimate: bool = False
|
|
25
|
+
# Skip the confirmation wait.
|
|
26
|
+
auto_confirm: bool = False
|
|
27
|
+
# Force a fresh fleet, skipping auto-resume.
|
|
28
|
+
new_fleet: bool = False
|
|
29
|
+
spot: bool = False
|
|
30
|
+
ami: str | None = None
|
|
31
|
+
worker_user: str = "root"
|
|
32
|
+
# Local script each worker runs as root before taking jobs, e.g. to install an external sandbox's host side.
|
|
33
|
+
worker_setup: str | None = None
|
|
34
|
+
|
|
35
|
+
@field_validator("instance_type_fallbacks", mode="before")
|
|
36
|
+
@classmethod
|
|
37
|
+
def _split_instance_type_fallbacks(cls, value: object) -> object:
|
|
38
|
+
if isinstance(value, str):
|
|
39
|
+
return [t.strip() for t in value.split(",") if t.strip()]
|
|
40
|
+
return value
|