Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5ad745e
Runtime worker recipe draft: openhands on GPT-6 through OmniRoute (pi…
Sep 27, 2026
c31a70f
Runtime worker openhands: round 2 (SWE-bench grading via OpenHands/be…
Sep 27, 2026
e838669
OpenHands runtime worker: round-3 repair, REST dispatch and both gate…
Sep 27, 2026
eb71663
test_runtime_worker_openhands: resolve temp roots for macOS
Sep 27, 2026
3a740b6
OpenHands runtime lock: linux/amd64 environments restriction plus fou…
Sep 27, 2026
708d2d1
OpenHands worker: phase-1 takeover repairs for #425 (F16-F20, session…
Sep 28, 2026
12a70db
OpenHands takeover phase 1 follow-up: bound F16, fix install image id…
Sep 28, 2026
f951134
OpenHands phase 2 (1/4): proxy base URL, recorded compression combos,…
Sep 28, 2026
c7eda5b
OpenHands phase 2 (2/4): O1 topology, pinned nginx allowlist proxy, p…
Sep 28, 2026
1a5047e
OpenHands phase 2 (3/4): P0-P2 containment probe, dispatch-start gate…
Sep 28, 2026
a0049a5
OpenHands phase 2 (4a/4): teardown action and README for O1, the prob…
Sep 28, 2026
6f1b5d6
OpenHands phase 2 (4b/4): E5 decision record, research corrections, e…
Sep 28, 2026
39aaebe
OpenHands phase 2: correct $S-int membership and host-side reach wording
Sep 28, 2026
1ee13eb
OpenHands phase 2: record the repository checks and their base control
Sep 28, 2026
a3a8818
OpenHands phase 2: bound the namespace claim and mark masked suite re…
Sep 28, 2026
153de40
OpenHands repair R1: grade with network mode none and inspect every g…
Sep 28, 2026
03480d3
OpenHands repair R2, R4 and addendum b: probe targets, route evidence…
Sep 28, 2026
5f29b5f
OpenHands repair R6/G7 and probe-receipt trust: re-derive the verdict…
Sep 28, 2026
30d2cd7
OpenHands repair R3 (G5): read each reachable gateway store read-only…
Sep 28, 2026
086b7ec
OpenHands repair R5 (source-checked), gw:8080 log format and key-file…
Sep 28, 2026
62f6a22
OpenHands repair round docs: scope the containment claims and record …
Sep 28, 2026
ec229a7
Rebase onto main 83229e24: register the PR's files; research notes #4…
Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/osv-scanner-lockfiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
"path": "blueprints/us-equities/data/requirements.lock",
"parser": "requirements.txt"
},
{
"path": "blueprints/runtime-workers/openhands/requirements.lock",
"parser": "requirements.txt"
},
{
"path": "blueprints/runtime-workers/openhands/build-requirements.lock",
"parser": "requirements.txt"
},
{
"path": "blueprints/us-equities/engine/patches/alpaca.packages.lock.json",
"parser": "packages.lock.json"
Expand Down
21 changes: 21 additions & 0 deletions adoption/credential-inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@
"rotation": "Regenerate through the host's own nativestack-generation service.",
"notes": "Host-local; no code in this repository reads it. Listed so the checker and the agent deny rules cover it."
},
{
"id": "openhands-session",
"label": "OpenHands agent-server session key (self-generated, one per attempt)",
"class": "local_service_secret",
"status": "generated_local",
"lane": "foundation-evaluation",
"store": {
"kind": "private_file",
"path_template": "$HOME/.local/state/native-agent-stack/runtime-workers/openhands/secrets/<run-id>-<arm>.server.env"
},
"variables": [],
"optional_variables": [],
"pointer_variables": [],
"loaders": [
"blueprints/runtime-workers/openhands/host.py#generate_session_files",
"blueprints/runtime-workers/openhands/dispatch.py#execute"
],
"environment_only_consumers": [],
"rotation": "Never reused: host.py generates a new value for every attempt and deletes both files after the attempt's containers are confirmed removed.",
"notes": "Generated with Python's secrets module and written 0600 with O_CREAT|O_EXCL|O_NOFOLLOW in a 0700 directory. <run-id>-<arm>.server.env holds one OH_SESSION_API_KEYS_0=<value> line in docker env-file syntax and is passed only through docker --env-file, so the variable exists only inside the agent-server container; the sibling <run-id>-<arm>.headers file carries the X-Session-API-Key header for curl -H @file. Never in argv, never printed; the server is never read with a full docker inspect. No operator-supplied value and no pointer variable. OH_SESSION_API_KEYS_0 is not a host variable, so it is not in variables."
},
{
"id": "claude-native",
"label": "Claude Code native sign-in",
Expand Down
1,094 changes: 1,094 additions & 0 deletions blueprints/runtime-workers/openhands/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
packaging==26.3 --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
setuptools==84.0.0 --hash=sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670
wheel==0.46.3 --hash=sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d
23 changes: 23 additions & 0 deletions blueprints/runtime-workers/openhands/config/host.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"variables": {
"ECO_ROOT": "<private installed ecosystem prefix>",
"HOST_PATH": "<container-compatible PATH for the existing MCP executables>",
"AI_MEMORY_URL": "10.0.2.2:49474",
"QDRANT_URL": "<private host service address reachable from the container>",
"EMBED_URL": "10.0.2.2:18232",
"CODE_INDEX_PATH": "/state/mcp/jcodemunch"
},
"mcp_readonly_mounts": [
"<private existing MCP executable/dependency directory>"
],
"qmd_collections": {
"foundation-docs": "<private foundation docs directory>",
"foundation-adoption": "<private adoption directory>",
"us-equities-foundation": "<private foundation blueprint directory>",
"us-equities-catalog": "<private US-equities catalog directory>"
},
"gateway_providers": {
"control": ["codex"],
"engines-on": ["openai-compatible-responses-*"]
}
}
28 changes: 28 additions & 0 deletions blueprints/runtime-workers/openhands/config/mcp-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"context-mode": {
"disabled_tools": ["ctx_upgrade", "ctx_purge"],
"env": {"CONTEXT_MODE_PROJECT_DIR": "/workspace"}
},
"serena": {"enabled": true},
"jcodemunch": {"enabled_tools": ["route", "menu", "order"]},
"qmd": {
"enabled_tools": ["query", "get", "multi_get", "status"],
"collections": ["foundation-docs", "foundation-adoption", "us-equities-foundation", "us-equities-catalog"]
},
"ai-memory": {
"enabled": false,
"enabled_tools": ["memory_query", "memory_read_page", "memory_recent", "memory_status", "memory_briefing"],
"reason": "Disabled because it is unreachable by design under O1: the internal run network has no route to host services, only three /v1 routes reach the gateway, and the proxy's other listener, gw:8080, reaches only the agent's own server (docs/decisions/2026-09-28-openhands-resolver-isolation.md)."
},
"socraticode": {
"enabled": false,
"enabled_tools": ["codebase_search", "codebase_status", "codebase_list_projects", "codebase_health"],
"env": {"SOCRATICODE_WATCHER": "manual"},
"reason": "Disabled because it is unreachable by design under O1: the internal run network has no route to host services (Qdrant, the embedder), only three /v1 routes reach the gateway, and the proxy's other listener, gw:8080, reaches only the agent's own server (docs/decisions/2026-09-28-openhands-resolver-isolation.md)."
},
"headroom": {
"enabled": false,
"enabled_tools": ["headroom_compress", "headroom_retrieve", "headroom_stats"],
"reason": "Native terminal output offload and LLMSummarizingCondenser cover this bounded task; no demonstrated remaining gap."
}
}
121 changes: 121 additions & 0 deletions blueprints/runtime-workers/openhands/config/mcp.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"context-mode": {
"command": "${ECO_ROOT}/bin/node",
"args": [
"${ECO_ROOT}/tools/context-mode-1.0.169/lib/node_modules/context-mode/start.mjs"
],
"env": {
"RTK_TELEMETRY_DISABLED": "1",
"PATH": "${ECO_ROOT}/bin:${HOST_PATH}",
"CONTEXT_MODE_PLATFORM": "codex",
"CONTEXT_MODE_PROJECT_DIR": "/workspace"
},
"transport": "stdio",
"timeout": 120,
"cwd": "/workspace"
},
"serena": {
"command": "${ECO_ROOT}/bin/serena",
"args": [
"start-mcp-server",
"--transport",
"stdio",
"--project",
"/workspace",
"--context",
"codex",
"--enable-web-dashboard",
"false",
"--open-web-dashboard",
"false",
"--enable-gui-log-window",
"false"
],
"env": {
"RTK_TELEMETRY_DISABLED": "1",
"PATH": "${ECO_ROOT}/bin:${HOST_PATH}",
"SERENA_HOME": "/state/mcp/serena/home"
},
"transport": "stdio",
"timeout": 120,
"cwd": "/workspace"
},
"jcodemunch": {
"command": "${ECO_ROOT}/bin/jcodemunch-mcp",
"env": {
"RTK_TELEMETRY_DISABLED": "1",
"PATH": "${ECO_ROOT}/bin:${HOST_PATH}",
"CODE_INDEX_PATH": "${CODE_INDEX_PATH}",
"JCODEMUNCH_SHARE_SAVINGS": "0"
},
"transport": "stdio",
"timeout": 120,
"cwd": "/workspace"
},
"qmd": {
"command": "${ECO_ROOT}/bin/qmd",
"args": [
"--index",
"native-agent-stack-catalog",
"mcp"
],
"transport": "stdio",
"timeout": 120,
"cwd": "/workspace",
"env": {}
},
"ai-memory": {
"enabled": false,
"url": "http://${AI_MEMORY_URL}/mcp",
"transport": "http",
"timeout": 120,
"env": {}
},
"socraticode": {
"enabled": false,
"command": "${ECO_ROOT}/bin/node",
"args": [
"${ECO_ROOT}/tools/socraticode-1.14.0/lib/node_modules/socraticode/dist/index.js"
],
"env": {
"RTK_TELEMETRY_DISABLED": "1",
"PATH": "${ECO_ROOT}/bin:${HOST_PATH}",
"QDRANT_MODE": "external",
"QDRANT_URL": "http://${QDRANT_URL}",
"EMBEDDING_PROVIDER": "lmstudio",
"LMSTUDIO_URL": "http://${EMBED_URL}/v1",
"EMBEDDING_MODEL": "nvidia/Nemotron-3-Embed-1B-BF16",
"EMBEDDING_DIMENSIONS": "2048",
"EMBEDDING_CONTEXT_LENGTH": "4096",
"EMBEDDING_QUERY_PREFIX": "query: ",
"EMBEDDING_DOCUMENT_PREFIX": "passage: ",
"EMBEDDING_DOCUMENT_INCLUDE_PATH": "true",
"RESPECT_GITIGNORE": "true",
"INCLUDE_DOT_FILES": "false",
"SOCRATICODE_WATCHER": "manual",
"SEARCH_DEFAULT_LIMIT": "3"
},
"transport": "stdio",
"timeout": 120,
"cwd": "/workspace"
},
"headroom": {
"command": "${ECO_ROOT}/bin/headroom",
"args": [
"mcp",
"serve",
"--proxy-url",
"http://127.0.0.1:1"
],
"env": {
"HEADROOM_OFFLINE": "1",
"HF_HUB_OFFLINE": "1",
"TRANSFORMERS_OFFLINE": "1",
"DO_NOT_TRACK": "1"
},
"transport": "stdio",
"timeout": 120,
"cwd": "/workspace",
"enabled": false
}
}
82 changes: 82 additions & 0 deletions blueprints/runtime-workers/openhands/config/proxy-nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Per-attempt gateway proxy for the O1 topology (README "Security posture";
# docs/decisions/2026-09-28-openhands-resolver-isolation.md).
#
# Composed locally from documented nginx directives; no upstream ships this
# file. Directive sources, fetched 2026-09-28: nginx.org ngx_http_core_module
# (location, limit_except, merge_slashes, client_max_body_size),
# ngx_http_proxy_module (proxy_pass, proxy_pass_request_headers,
# proxy_set_header, proxy_http_version, proxy_buffering,
# proxy_request_buffering) and ngx_http_rewrite_module (if, return). Source
# lines cite nginx/nginx release-1.30.5 (4556c714).
#
# host.py render_proxy_config fills the four placeholders (gateway port,
# server name, run id, compression combo) from recipe.arm_config and the
# attempt identity, and refuses any value outside its own pattern.
pid /tmp/nginx.pid;
error_log /dev/stderr warn;
events { worker_connections 256; }
http {
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
server_tokens off;
access_log /dev/stdout;
# The default combined format logs the whole request line, "$request"
# (src/http/modules/ngx_http_log_module.c:230-232). The host side uses this
# one instead, so its access log does not copy a URI sent to gw:8080.
# Request-bound error-log entries still carry the request line
# (src/http/ngx_http_request.c:4103-4107). log_format is accepted only at
# http level (NGX_HTTP_MAIN_CONF, ngx_http_log_module.c:170-171).
log_format ingress '$time_iso8601 $request_method $status';

# Agent side: gw:8081, reached from the internal run network.
server {
listen 8081;
client_max_body_size 50m;
proxy_http_version 1.1;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 900s;
proxy_send_timeout 900s;
# A proxy_pass that carries a URI still appends the request arguments
# (ngx_http_proxy_module.c:1240-1251,1365-1384). $is_args is "?"
# exactly when arguments exist (ngx_http_variables.c:1609-1624) and is
# never "0", so no agent-chosen query string reaches the gateway.
if ($is_args) { return 403; }
# Header allowlist: no client header reaches the gateway unless it is
# set here. A header whose value is empty is not sent
# (ngx_http_proxy_module.c:1301-1303,1417-1418), so the control arm's
# empty compression value drops that header.
proxy_pass_request_headers off;
proxy_set_header Host "10.0.2.2:@PORT@";
proxy_set_header Content-Type $content_type;
proxy_set_header Accept $http_accept;
proxy_set_header Idempotency-Key $http_idempotency_key;
proxy_set_header Authorization "Bearer local-loopback";
proxy_set_header X-Correlation-Id "@RUN@";
proxy_set_header x-omniroute-session "@RUN@";
proxy_set_header x-omniroute-compression "@COMPRESSION@";
# Exact matches on the normalized URI (percent-decoding, dot segments
# and merged slashes are resolved first); each forwards to one fixed
# upstream URI, and limit_except refuses every other method.
location = /v1/responses { limit_except POST { deny all; } proxy_pass http://10.0.2.2:@PORT@/v1/responses; }
location = /v1/chat/completions { limit_except POST { deny all; } proxy_pass http://10.0.2.2:@PORT@/v1/chat/completions; }
location = /v1/models { limit_except GET { deny all; } proxy_pass http://10.0.2.2:@PORT@/v1/models; }
location / { return 403; }
}

# Host side: 127.0.0.1:<port> is published to 8080 and forwarded to the
# attempt's agent-server. The dispatch driver's event search carries a
# query string, so this block has no argument refusal. Its access log
# records time, method and status only; the agent side (8081) keeps the
# combined format, which P5 triage reads.
server {
listen 8080;
access_log /dev/stdout ingress;
client_max_body_size 50m;
proxy_read_timeout 900s;
location / { proxy_pass http://@SERVER@:8000; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Serena c6fbd1c5932df2494ffa0020af5a9fbe80b82143:
# src/serena/config/serena_config.py:928-936,1050-1104.
# Missing values are completed by the native loader in the private owned copy.
projects: []
web_dashboard: false
web_dashboard_open_on_launch: false
gui_log_window: false
project_serena_folder_location: "/state/mcp/serena/projects/$projectFolderName"
default_max_tool_answer_chars: 30000
49 changes: 49 additions & 0 deletions blueprints/runtime-workers/openhands/config/worker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"schema_version": 1,
"model_env": "OPENHANDS_MODEL",
"arm_env": "OPENHANDS_ARM",
"base_url_env": "OPENHANDS_BASE_URL",
"compression_env": "OPENHANDS_COMPRESSION",
"llm": {
"model": "cx/gpt-6-astra-max",
"model_canonical_name": "openai/gpt-6",
"base_url": "http://gw:8081/v1",
"api_key": "local-loopback",
"api_mode": "responses",
"reasoning_effort": "max",
"stream": true,
"native_tool_calling": true,
"max_output_tokens": 16384,
"num_retries": 2,
"timeout": 180,
"capability_overrides": {
"supports_reasoning_effort": true,
"supports_sampling_params": false,
"supports_responses_api": true
}
},
"runtime": {
"agent_loop": "container",
"llm_calls": "container",
"stdio_mcp": "container",
"gateway_base_url": "http://gw:8081/v1",
"published_ports": ["127.0.0.1:3730:8080"],
"max_iteration_per_run": 40,
"timeout_seconds": 1200,
"cpus": "4",
"memory": "8g",
"pids_limit": 384
},
"condenser": {
"kind": "LLMSummarizingCondenser",
"max_size": 80,
"keep_first": 2,
"max_tokens": 60000
},
"agent_context": {
"load_user_skills": false,
"load_public_skills": false,
"load_project_skills": false,
"load_memory": false
}
}
Loading
Loading