Skip to content

verify: upstream #64190 on its true base (tenki runners) - #948

Open
hashbender wants to merge 4 commits into
verify-base/pr-64190from
verify/pr-64190
Open

verify: upstream #64190 on its true base (tenki runners)#948
hashbender wants to merge 4 commits into
verify-base/pr-64190from
verify/pr-64190

Conversation

@hashbender

Copy link
Copy Markdown
Owner

Verification build for upstream NousResearch#64190 (Tenki cloud sandbox terminal backend).

Unlike mirror PR #947 — whose file overlay landed on the 11-day-stale mirror base and broke on an unrelated import (skill_matches_platform_list didn't exist in the old agent/skill_utils.py) — this PR cherry-picks the actual PR commits onto their actual upstream base commit (c44de99) with only the workflow runner labels tenkified. CI results here reflect the real upstream PR.

Deliberately NOT marked as a mirror so the watcher keeps tracking #947.

Nick and others added 2 commits July 13, 2026 23:08
Adds Tenki (tenki.cloud) as a seventh terminal execution backend alongside
local, docker, ssh, singularity, modal, and daytona. Hermes creates Tenki
sandboxes on demand for terminal, file tools, and execute_code, and
terminates them on cleanup by default (opt-in pause/resume persistence via
container_persistent: true).

Core:
- tools/environments/tenki.py: TenkiEnvironment — sandbox lifecycle, exec,
  pause/resume persistence, remote file sync-back
- tools/tenki_config.py: profile-scope-aware auth/workspace/project/endpoint
  resolution from the Tenki CLI config or environment
- Shared _container_config_from_env_config() helper replaces the three
  duplicated container-config dicts (terminal, file tools, execute_code)
- Setup wizard, doctor, status, gateway, and CLI wiring; website docs,
  env-var reference, and cli-config.yaml.example
- Optional tenki extra (tenki-sandbox==0.1.1), lazy-installed like
  modal/daytona

Security & correctness hardening:
- Do not inject the supervisor's control-plane Tenki token into the
  model-controlled guest env; host-side SDK auth is unchanged. Nested-sandbox
  creation is an explicit opt-in via terminal.tenki_forward_env (which also
  forwards the resolved token so `tenki login` credentials work), and logs a
  warning when the control-plane token is forwarded.
- Resolve Tenki credentials and forwarded env through agent.secret_scope so an
  active profile scope wins over process-global os.environ and the shared
  machine CLI login is skipped when a profile scope is authoritative.
- Strip TENKI_AUTH_TOKEN / TENKI_API_KEY from spawned subprocess environments
  (provider blocklist + always-strip tier), matching modal/daytona.
- Namespace persistent sandbox identity by a per-profile token (name +
  metadata + reuse match) and resolve the snapshot-store path per profile,
  bound at construction so background-thread cleanup writes to the right home.
- Durability gate: a non-durable snapshot is not recorded (cleanup pauses and
  preserves prior state); a failed pause leaves the sandbox live rather than
  terminating it. Restore falls back to a base image only for an unrecoverable
  snapshot (gone / non-durable / snapshot-specific invalid state), preserving
  the pointer on transient errors.
- Config: blank tenki_api_endpoint default across both config loaders so the
  documented env/CLI fallback is reachable; allow the guest-home subtree
  (/home/tenki/*) as a valid cwd at all container-cwd guards.

Known follow-up (pre-existing, backend-agnostic): the process-global terminal
environment cache (_active_environments, keyed "default") is not profile-scoped,
so under the multiplexing gateway forwarded credentials are not isolated across
profiles. Tracked separately; documented in the credential-forwarding notes.

Tests: tests/tools/test_tenki_environment.py plus terminal/file/config/scrub
coverage, including profile-scope, durability, restore-classification, and
cwd-subtree regression pins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Required by contributor-check for nick@luxor.tech commits in NousResearch#64190.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hashbender

Copy link
Copy Markdown
Owner Author

@tenki-reviewer review this PR — full review please. This is the true-base build of upstream NousResearch#64190 (Tenki cloud sandbox terminal backend); the diff here is exactly what's proposed upstream.

@tenki-reviewer

tenki-reviewer Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Complete

Files Reviewed: 63
Findings: 2

By Severity:

  • 🟠 High: 1
  • 🟢 Low: 1

This PR adds the Tenki cloud sandbox backend across the Hermes codebase with new config keys, environment adapter, and tool integration. Review found 4 issues, including two high-severity: a stuck cleanup flag that permanently bricks the sandbox environment, and cross-profile credential reuse under the multiplexing gateway that can leak Tenki API credentials between profiles.

Files Reviewed (63 files)
AGENTS.md
CONTRIBUTING.md
README.md
agent/prompt_builder.py
cli-config.yaml.example
cli.py
docs/security/network-egress-isolation.md
gateway/run.py
hermes_cli/config.py
hermes_cli/doctor.py
hermes_cli/setup.py
hermes_cli/status.py
hermes_cli/tips.py
hermes_cli/web_server.py
nix/packages.nix
pyproject.toml
scripts/release.py
tests/agent/test_prompt_builder.py
tests/gateway/test_config_cwd_bridge.py
tests/hermes_cli/test_config_env_expansion.py
tests/hermes_cli/test_setup.py
tests/test_project_metadata.py
tests/tools/test_browser_ssrf_local.py
tests/tools/test_command_guards.py
tests/tools/test_container_cwd_sanitize.py
tests/tools/test_docker_network_config.py
tests/tools/test_file_tools_container_config.py
tests/tools/test_hardline_blocklist.py
tests/tools/test_local_env_blocklist.py
tests/tools/test_modal_sandbox_fixes.py
tests/tools/test_parse_env_var.py
tests/tools/test_skills_tool.py
tests/tools/test_tenki_environment.py
tests/tools/test_terminal_config_env_sync.py
tests/tools/test_terminal_requirements.py
tests/tools/test_terminal_tool_requirements.py
tools/approval.py
tools/browser_tool.py
tools/code_execution_tool.py
tools/env_probe.py
tools/environments/__init__.py
tools/environments/base.py
tools/environments/local.py
tools/environments/tenki.py
tools/file_operations.py
tools/file_tools.py
tools/lazy_deps.py
tools/skills_tool.py
tools/tenki_config.py
tools/terminal_tool.py
tools/tool_result_storage.py
uv.lock
website/docs/developer-guide/architecture.md
website/docs/getting-started/nix-setup.md
website/docs/guides/tips.md
website/docs/index.mdx
website/docs/reference/environment-variables.md
website/docs/user-guide/configuration.md
website/docs/user-guide/features/tools.md
website/docs/user-guide/security.md
website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md
website/docs/user-guide/skills/bundled/software-development/software-development-plan.md
website/scripts/generate-llms-txt.py

@tenki-reviewer tenki-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: 🟠 High (72/100) — 1 high finding, 1 low · 3881 LOC across 63 files


Overview

PR #948 introduces the Tenki cloud sandbox backend, adding a new environment adapter (tools/environments/tenki.py), 12 new config keys under env.tools.backends.tenki, env-var bridge wiring across CLI/gateway/tools, and security controls for credential isolation and env-blocklisting.

High-Severity Findings

Cross-profile credential reuse (finding-004)

The TenkiEnvironment bakes host-side credentials (_auth_token, _workspace_id, _project_id) into instance attributes at construction time. The shared environment cache _active_environments in tools/terminal_tool.py keys environments by effective_task_id, which collapses to 'default' for all profiles without isolation-keyed overrides. Under the multiplexing gateway, a second profile reuses the first profile's cached TenkiEnvironment — creating sandboxes, executing commands, and incurring billing against the wrong account.

Stuck cleanup flag bricks environment (finding-002)

TenkiEnvironment.cleanup() sets _cleanup_in_progress = True before calling _close_client(), which calls client.close() without exception handling. If close() raises (e.g., HTTP connection error), the exception propagates past the _cleanup_in_progress = False reset — permanently blocking all future sandbox operations until process restart.

Medium/Low-Severity Findings

  • missing 'tenki' in _CONTAINER_PATH_BACKENDS_FALLBACK (tools/file_tools.py:167): The fallback frozenset omits 'tenki' while the primary constant _CONTAINER_BACKENDS includes it. On import-failure code paths, file operations for tenki backends use host-path semantics instead of container-path semantics.
  • prompt_builder probe missing two tenki keys (agent/prompt_builder.py:1008): The inline container_config dict omits tenki_sync_hermes_home and tenki_forward_env that the canonical shared builder includes, creating a maintenance drift hazard.

Assessment

Two high-severity issues warrant changes before merge: credential isolation under multiplexing and cleanup exception safety. The remaining two findings are lower risk but represent real consistency gaps in the tenki backend integration.

Comment thread tools/environments/tenki.py Outdated
Comment on lines +1082 to +1087
def _close_client(client: Any) -> None:
if client is None:
return
close = getattr(client, "close", None)
if callable(close):
close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Unhandled exception in _close_client permanently bricks TenkiEnvironment via stuck _cleanup_in_progress (bug)

TenkiEnvironment.cleanup() sets _cleanup_in_progress = True at line 978, then calls _close_client(client) in the finally block (line 1038) and in the sandbox-is-None early-return branch (line 981). _close_client() (lines 1082-1087) calls client.close() without an exception handler. If close() raises, the exception escapes, skipping the _cleanup_in_progress = False reset (lines 985 and 1044). All subsequent _ensure_sandbox() calls raise RuntimeError, permanently bricking the Tenki environment until process restart.

💡 Suggestion: Wrap the close() call inside _close_client with a try/except that logs and swallows exceptions, so no exception propagates back to cleanup()'s finally block or the sandbox-is-None branch.

📋 Prompt for AI Agents

In tools/environments/tenki.py, modify the _close_client static method (lines 1082-1087) to wrap the close() call at line 1087 in a try/except block that catches all exceptions, logs them at debug level, and does not re-raise. This ensures _close_client never propagates exceptions, allowing cleanup() to always reset _cleanup_in_progress to False.

Comment thread agent/prompt_builder.py Outdated
"tenki_max_duration": config.get("tenki_max_duration", 3600),
"tenki_idle_timeout": config.get("tenki_idle_timeout", 0),
"tenki_pause_retention": config.get("tenki_pause_retention", 0),
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Probe container_config in prompt_builder.py missing two tenki keys compared to shared builder (bug)

agent/prompt_builder.py:_probe_remote_backend() builds its own container_config dict inline (lines 984-1008) for the tenki backend, but omits tenki_sync_hermes_home and tenki_forward_env that the canonical shared builder _container_config_from_env_config at tools/terminal_tool.py:1440-1467 includes. All three runtime callers (terminal_tool, code_execution_tool, file_tools) use the shared builder; only the prompt-builder probe hand-rolls its own dict. Since _create_environment defaults these safely, there is no runtime bug today — but the two dicts have already drifted and present a maintenance hazard.

💡 Suggestion: Either refactor the probe to call _container_config_from_env_config(config) instead of building its own inline dict, or add the missing tenki_sync_hermes_home and tenki_forward_env keys so the two definitions stay in sync.

📋 Prompt for AI Agents

In agent/prompt_builder.py, inside _probe_remote_backend() at lines 984-1008, add the two missing keys just before the closing brace of the container_config dict (after line 1007):
"tenki_sync_hermes_home": config.get("tenki_sync_hermes_home", False),
"tenki_forward_env": config.get("tenki_forward_env", []),
Alternatively, refactor to import and call _container_config_from_env_config(config) from tools.terminal_tool to eliminate the duplication entirely.

Nick and others added 2 commits July 14, 2026 12:00
…l race

cancel() can null self._sandbox between _ensure_sandbox() and the
dereference in _start_process/_exec_raw/_transfer_sandbox, turning a
user interrupt into an AttributeError. _require_sandbox() captures the
reference under the lock and raises a typed RuntimeError if the sandbox
was torn down. Found by Tenki Code Reviewer on the mirrored PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two findings from Tenki Code Reviewer on the true-base verification PR:

- _close_client: swallow close() exceptions. cleanup() resets
  _cleanup_in_progress only after closing the client, so an escaping
  network error during teardown left the flag stuck and every later
  _ensure_sandbox() failed with 'Tenki cleanup is in progress'.
- prompt_builder probe: replace the fourth inline container-config copy
  with the shared _container_config_from_env_config() builder; the
  inline dict omitted tenki_sync_hermes_home, tenki_forward_env, and
  docker_network, so probe environments diverged from real ones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant