Python: Add agent-framework-tenki (Tenki-backed CodeAct provider)#7312
Python: Add agent-framework-tenki (Tenki-backed CodeAct provider)#7312Patricio-Filice-Luxor wants to merge 6 commits into
Conversation
Introduces agent-framework-tenki, a third code-executor backend that runs Python inside a Tenki managed Linux microVM sandbox. Ships alongside the existing hyperlight (WASM) and monty (in-process Rust) executors, and mirrors their public API shape (TenkiCodeActProvider ContextProvider + TenkiExecuteCodeTool FunctionTool). Lifecycle: lazy provision, reuse-per-tool, and per-call reconciliation — PAUSED sandboxes are transparently resumed, TERMINATED sandboxes are replaced by a fresh provision. Includes 32 hermetic unit tests plus one opt-in integration test guarded on TENKI_API_KEY. Alpha-policy compliant: workspace uv sources only, no core[all] extra, no lazy-loading shim.
- Retry sandbox resume within a 120s poll budget: USER_SHUTDOWN pause snapshots are linked asynchronously (~60s) and the server can revert an accepted resume, so single-shot resume failed live - Keep run-scoped provider state JSON-serializable (store sandbox name in session state; live tool handles stay on the provider) - Handle CommandResult diagnostics (ok/signal/reason/errno) directly and keep the sandbox handle when close/refresh fails so it can retry - Run tool close under a single lock in a worker thread so a close during an in-flight reconcile cannot block the event loop - Make provider.close() retryable: keep failed terminates in the live-tool set instead of dropping them up front - Type __aexit__ signatures to match other resource-backed providers - Pin tenki-sandbox>=0.4.0,<0.5, finite max_duration default (900s), document pause/terminate asymmetry on expiry - Correct startup-latency docs to measured ~2s (was 10-30s) and clarify run-scoped vs standalone sandbox lifetime in the README - Expand tests to 58 unit + 4 integration (resume retry, server revert, close/run serialization, retryable provider close, cancellation teardown, terminal-state re-provision); wire tenki into integration/merge CI workflows Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-tenki - Hold the sandbox lock across reconcile + exec so close() cannot terminate a sandbox while an exec is in flight - Add pause_retention_seconds kwarg (tool + provider); run-scoped sandboxes default to 1h retention so orphans are GC'd server-side - Provider close() raises RuntimeError after attempting all terminates, retaining failed handles for retry - Harden the teardown integration test: assert provisioning succeeded and poll the live API until TERMINATED - Clarify README max_duration None-semantics and pause retention Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`snapshot_id` restores from a snapshot prepared beforehand with the Tenki CLI/SDK — it does not snapshot the (already terminated) sandbox, so the previous wording suggested an impossible workflow. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@Patricio-Filice-Luxor please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
There was a problem hiding this comment.
Pull request overview
Adds a new Python alpha package, agent-framework-tenki, that implements a Tenki Sandbox–backed CodeAct backend. This extends the Agent Framework’s CodeAct provider/tool ecosystem with a remote, isolated Linux micro-VM execution environment (persistent filesystem across calls, subprocesses, package installs), alongside existing in-process backends.
Changes:
- Introduces
agent-framework-tenkipackage withTenkiCodeActProvider(run-scoped) andTenkiExecuteCodeTool(standalone, reusable sandbox). - Adds a runnable Tenki CodeAct sample and updates the CodeAct samples README to include the third backend.
- Wires Tenki into the Python workspace/lockfile and includes its integration tests in CI workflows.
Reviewed changes
Copilot reviewed 13 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| python/uv.lock | Registers agent-framework-tenki workspace member and locks tenki-sandbox dependency. |
| python/pyproject.toml | Adds agent-framework-tenki to the Python workspace packages. |
| python/PACKAGE_STATUS.md | Marks the new package as alpha. |
| python/samples/02-agents/context_providers/code_act/tenki_code_act.py | New sample demonstrating Tenki-backed provider usage. |
| python/samples/02-agents/context_providers/code_act/README.md | Updates sample matrix + install/run instructions to include Tenki. |
| python/packages/tenki/README.md | Package documentation covering configuration, lifecycle, and Tenki-specific passthrough options. |
| python/packages/tenki/pyproject.toml | New package metadata, dependencies, typing, lint/test configuration. |
| python/packages/tenki/LICENSE | Adds MIT license file for the new package. |
| python/packages/tenki/agent_framework_tenki/py.typed | Marks the package as typed (PEP 561). |
| python/packages/tenki/agent_framework_tenki/init.py | Public exports for provider/tool + version wiring. |
| python/packages/tenki/agent_framework_tenki/_provider.py | Implements run-scoped provider lifecycle (before_run/after_run/close). |
| python/packages/tenki/agent_framework_tenki/_execute_code_tool.py | Implements sandbox lifecycle reconciliation + execute_code tool behavior. |
| python/packages/tenki/tests/tenki/test_tenki_codeact.py | Unit + opt-in integration tests for tool/provider lifecycle and result parsing. |
| .github/workflows/python-merge-tests.yml | Adds Tenki package tests to the misc integration test job; exports Tenki env vars. |
| .github/workflows/python-integration-tests.yml | Same as above for scheduled/manual integration workflow. |
Comments suppressed due to low confidence (3)
python/packages/tenki/tests/tenki/test_tenki_codeact.py:1373
- These integration tests pass project_id=os.environ.get("TENKI_PROJECT_ID") directly. In GitHub Actions, an unset
vars.TENKI_PROJECT_IDexpands to an empty string, which then gets forwarded as project_id="" and can cause Tenki provisioning to fail even though project_id is optional (single-project API keys). Consider only passing project_id when it’s a non-empty string.
project_id = os.environ.get("TENKI_PROJECT_ID")
async with TenkiExecuteCodeTool(
sandbox_name=f"agent-framework-ci-fs-{os.getpid()}",
project_id=project_id,
max_duration_seconds=300,
python/packages/tenki/tests/tenki/test_tenki_codeact.py:1392
- These integration tests pass project_id=os.environ.get("TENKI_PROJECT_ID") directly. In GitHub Actions, an unset
vars.TENKI_PROJECT_IDexpands to an empty string, which then gets forwarded as project_id="" and can cause Tenki provisioning to fail even though project_id is optional (single-project API keys). Consider only passing project_id when it’s a non-empty string.
project_id = os.environ.get("TENKI_PROJECT_ID")
async with TenkiExecuteCodeTool(
sandbox_name=f"agent-framework-ci-fail-{os.getpid()}",
project_id=project_id,
max_duration_seconds=300,
python/packages/tenki/tests/tenki/test_tenki_codeact.py:1422
- This integration test passes project_id=os.environ.get("TENKI_PROJECT_ID") directly. In GitHub Actions, an unset
vars.TENKI_PROJECT_IDexpands to an empty string, which then gets forwarded as project_id="" and can cause Tenki provisioning to fail even though project_id is optional (single-project API keys). Consider only passing project_id when it’s a non-empty string.
project_id = os.environ.get("TENKI_PROJECT_ID")
unique_name = f"agent-framework-ci-teardown-{os.getpid()}"
async with TenkiExecuteCodeTool(
sandbox_name=unique_name,
project_id=project_id,
max_duration_seconds=300,
) as tool:
…t-integration # Conflicts: # python/PACKAGE_STATUS.md # python/samples/02-agents/context_providers/code_act/README.md
CI systems expand unconfigured secrets/vars to "" (e.g. GitHub Actions vars.TENKI_PROJECT_ID), which was forwarded to the Tenki SDK as project_id=""/auth_token="" and failed provisioning in non-obvious ways. Env fallbacks now normalize "" to unset; explicit constructor args keep their documented precedence. Integration tests no longer pass an empty project_id as an explicit arg. Addresses Copilot review on microsoft#7312. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Motivation & Context
Adds a third CodeAct backend,
agent-framework-tenki, wrapping Tenki Sandbox (managed Linux micro-VMs) behind the same*CodeActProvider/*ExecuteCodeToolshape asagent-framework-hyperlightandagent-framework-monty. Unlike the existing in-process backends it provides a remote, isolated, real Linux environment: pip/apt installs, subprocesses, and a persistent filesystem across calls.Full rationale, backend comparison, and scope discussion: #7311
Description & Review Guide
python/packages/tenki/(1.0.0a260722, PEP 561 typed) withTenkiCodeActProvider(run-scoped: freshexecute_codetool + sandbox per agent run, terminated inafter_runso state never leaks across runs) andTenkiExecuteCodeTool(standalone: one sandbox reused untilclose()); a runnable sample; 66 unit + 4 opt-in integration tests; CI wiring.agent-framework[all]and noagent_framework.tenkilazy-loading shim (both deferred to beta promotion, per the alpha package policy). No existing package modified beyondPACKAGE_STATUS.md, workspace registration, and the CI workflow env.close()can reap runs that never reachedafter_run).Sandbox lifecycle (highlights):
execute_codecall, reused per tool instance. Before each call the tool refreshes remote state:PAUSED/USER_SHUTDOWN→ auto-resume, retried within a 120s poll budget (USER_SHUTDOWNlinks its pause snapshot asynchronously — verified live end-to-end);TERMINATED/TERMINATING→ re-provision;refresh()failure → structured error, handle kept for retry.sandbox.execrun under a single lock hold:close()serializes with in-flight execs, a failed terminate preserves the handle for retry, and the provider'sclose()attempts every leaked run tool and raisesRuntimeErrornaming the survivors so a secondclose()retries exactly those.max_duration_seconds(default 900s) stops compute billing server-side even if the client process crashes; provider run-scoped sandboxes cap pause-snapshot retention at 1h (standalone keeps Tenki's 7-day default).Implementation notes: sync SDK (
tenki-sandbox>=0.4.0,<0.5) bridged viaasyncio.to_thread+threading.Lock; typedCommandResultparsing (signal-killed processes correctly reported as failures); injected CodeAct instructions cover small-model footguns (print(...)requirement,subprocessfor pip, no Jupyter magic); explicit constructor args — including empty string — always win over env fallbacks (TENKI_API_KEY,TENKI_PROJECT_ID,TENKI_WORKSPACE_ID); Tenki-specific options (snapshot_id,volumes, network policy, …) pass throughextra_create_kwargs.Tests: 66 hermetic unit tests through the public
tool.invokeAPI (full reconcile matrix, kwargs forwarding, close semantics, provider run-scoping,CancelledErrorpropagation); 4 opt-in integration tests against the live service, guarded onTENKI_API_KEY. Note for maintainers: the integration job needssecrets.TENKI_API_KEYandvars.TENKI_PROJECT_IDconfigured.Out of scope for the alpha: host tool callbacks (the Tenki SDK has no bridge), dedicated constructor parameters for mounts/network/snapshots (reachable today via
extra_create_kwargs), and a session-scoped sandbox mode.Related Issue
Resolves #7311
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.