Skip to content

feat: add Tenki Cloud compute provider - #1

Open
rishijoshi wants to merge 1294 commits into
mainfrom
feat/tenki-compute-provider
Open

feat: add Tenki Cloud compute provider#1
rishijoshi wants to merge 1294 commits into
mainfrom
feat/tenki-compute-provider

Conversation

@rishijoshi

Copy link
Copy Markdown
Owner

What & why

PraisonAI already ships compute providers for E2B, Daytona, Modal, Fly.io, Docker and local (one file per vendor in integrations/compute/). This adds Tenki Cloud as another option — disposable Linux microVMs — so managed-agent tools can run in Tenki sandboxes.

What it does

  • TenkiCompute implements the full ComputeProviderProtocol (provision / execute / shutdown / get_status / upload_file / download_file / list_instances), running tools in ephemeral Tenki microVMs. Sync SDK wrapped in run_in_executor, exactly like DaytonaCompute/E2BCompute.
  • Registered as "tenki" in the compute barrel (__init__.py), the _resolve_compute factory (managed_local.py), and the provider hint sets (managed_agents.py, hosted_agent.py).
  • Enabled via TENKI_API_KEY; optional tenki extra (tenki-sandbox). Auto-resolves workspace/project from the key.

Feature scope

Uses only stable Tenki features — ephemeral exec + file I/O (base64 over exec). No volume/snapshot/template. The default stock image ships python3; pip packages are installed on demand (config.packages). Set config.metadata["tenki_image"] to use a prebaked image instead.

Testing

  • Unit tests (no creds): importable, provider_name, is_available, protocol methods, nonexistent-instance handling, and the barrel export — mirroring the E2B/Daytona suites in test_cloud_compute.py.
  • Live integration tests (skipped unless TENKI_API_KEY is set): provision → execute → file upload/download → shutdown, plus pip-install.
  • Validated live end-to-end against real Tenki: provisioned a microVM, pip-installed pandas on the stock image and ran it, round-tripped files, and shut down cleanly with no leaked sandbox.

@rishijoshi

Copy link
Copy Markdown
Owner Author

cc @camcalaquian — opened in my fork per your note (holding upstream for your review gate). Adds Tenki as a 7th compute provider alongside E2B/Daytona/Modal/Fly.io/Docker/local (both E2B & Daytona already in-tree, per the 'prioritize competitor-integrated repos' steer). Stable features only (exec + file I/O), no volume/snapshot. Validated live vs real Tenki: provision + pandas pip-install + exec + file round-trip + clean teardown.

@camcalaquian camcalaquian 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.

overall lgtm. once the version is updated let's work on getting this to upstream

Comment thread src/praisonai/pyproject.toml Outdated
# Install via: pip install git+https://github.com/daytonaio/daytona-python
]
tenki = [
"tenki-sandbox>=0.3",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can we make the version >=0.3.6

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Done in 9354602 — bumped to tenki-sandbox>=0.3.6. Ready for the upstream step whenever you are.

@camcalaquian

Copy link
Copy Markdown

@rishijoshi sorry there was another release of sdk yesterday, let's update to that and submit this PR to upstream.

@rishijoshi

Copy link
Copy Markdown
Owner Author

@camcalaquian thanks! Quick check before I bump — PyPI's latest tenki-sandbox is still 0.3.6 (uploaded 2026-07-10), which is what this PR already pins (>=0.3.6). I don't see a newer release there yet — checked including pre-releases, and pip index versions agrees (only 0.1.0 / 0.1.1 / 0.3.5 / 0.3.6). The tenki CLI is separately at v0.16.0, but that's not the pyproject dep.

Could you point me at the new version (or where it's published)? As soon as it's on PyPI I'll bump tenki-sandbox here (and in the khoj PR), re-lock, re-validate, and open the upstream PR.

@camcalaquian

Copy link
Copy Markdown

seems the updated sdk wasn't posted immediately, you should see the updated sdk now on pypi

@rishijoshi

Copy link
Copy Markdown
Owner Author

0.4.0 landed on PyPI (2026-07-17) — bumped to tenki-sandbox>=0.4.0 (68c8ebf) and re-validated live against 0.4.0 (sync Client path: provision + exec + teardown, green). Submitted upstream: MervinPraison#3242.

praisonai-triage-agent Bot and others added 24 commits August 4, 2026 11:17
…-3649-20260804-1008

fix: wire delegate_task tool to subagent runtime
…-3657-20260804-1008

fix: OpenAI-compatible ManagerInstructions schema for hierarchical process
…-3651-20260804-1008

feat: unified remember/recall/forget convenience API for Memory
…-3655-20260804-1008

fix: ASCII-safe capabilities in models CLI on Windows cp1252
…-3650-20260804-1008

feat: add praisonai init team multi-agent project scaffold
…-3654-20260804-1008

fix: migrate invalid AgentTeam kwargs to output=/execution=
…-3666-20260804-1027

docs: add minimal sequential team YAML example (fixes MervinPraison#3666)
…-3665-20260804-1027

docs: correct-signature human-review team example + regression test
…ixes MervinPraison#3669)

Add the missing repo-committed environment definition joint: a lightweight
loader that maps .praisonai/environment.yaml onto the existing ComputeConfig
schema, plus the one new field (setup) with a live consumer.

- Core (praisonaiagents): ComputeConfig.setup: List[str]; load_environment_definition()
  and find_environment_definition() (walks up like config discovery). YAML is
  lazy-imported; unknown top-level keys raise ValueError with the file path.
  network/backend preferences carried in existing typed fields (no new surface).
- Live consumer: docker compute provider runs setup commands once post-provision,
  streamed to logs, with failures surfaced (raised) rather than swallowed.
- No file -> byte-identical behaviour (loader returns None; callers keep defaults).

Deliberately minimal per AGENTS.md (lightweight & powerful): no new CLI group,
no dispatcher rewrite, no SandboxConfig bridge, no RuntimeConfig remap.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…, clean up leaked container

Addresses the three Greptile P1 findings on PR MervinPraison#3671:
- Wire load_environment_definition() into provision_compute() as an opt-in
  baseline (explicit kwargs/instance config still win) so a committed
  .praisonai/environment.yaml is actually applied.
- Validate nested config shapes in the loader so malformed values raise a
  contextual ValueError (with file path) instead of incidental
  AttributeError/TypeError.
- Tear down the just-started Docker container if package install or setup
  fails, so a failed provision no longer leaks an unreachable container +
  registry entry.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
…-3669-20260804-1506

feat: add .praisonai/environment.yaml loader + ComputeConfig.setup
…vinPraison#3672)

Add functools.lru_cache to the pure model-capability predicates so the
same (model_name -> bool) result is computed once per process instead of
on every request/turn. Behaviour is unchanged: guards, litellm-None
fallbacks and except paths are all preserved, and keys are hashable
strings.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…son#3674)

PraisonAIAgents was exported from praisonaiagents.agents but omitted from
the root lazy-import map and __all__, so `from praisonaiagents import
PraisonAIAgents` raised ImportError. Added it alongside its siblings and a
regression test covering all back-compat aliases.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…contract (MervinPraison#3674)

Two existing tests asserted PraisonAIAgents raises ImportError (a v4
"removed" decision). Issue MervinPraison#3674 (owner) explicitly restores it as a
root-level silent alias for AgentTeam, which PR MervinPraison#3675 implements. Update
those tests to assert the restored back-compat contract instead.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
…-3674-20260804-2348

fix: expose PraisonAIAgents alias from package root
…-3672-20260804-1715

fix: memoise pure model-capability lookups in LLM hot path
…-3636-20260804-0853

feat: declarative per-tool model-facing output (model_output/to_model_output) for context economy
…eleases

Multi-agent gap review of the release pipeline surfaced 17 findings across
the gate, the workflow, and the bump script. This fixes the confirmed ones.

Gate (release-gate.js + nightly-release-gate.yml):
- A pypi-release run stuck in `waiting` (environment approval) blocked all
  auto-releases with no age limit -- GitHub only auto-fails unapproved
  deployments after 30 days, and exactly this stalled releases for 16h this
  week. Waiting runs older than 6h are now ignored (with a warning linking
  the run); in_progress/queued runs still block unconditionally, and the
  stuck run is never cancelled -- if later approved, the concurrency group
  serializes it and pypi_exists no-ops anything already published.
- PACKAGE_PATHS covered 3 of 9 published packages, so a change landing only
  in e.g. praisonai-bot was never auto-released. Now lists all nine.
- The 3-day dedupe counted any successful run -- including dry_run=true,
  which publishes nothing -- blocking real releases for 3 days after a dry
  run. Dedupe now keys on v* GitHub releases, which only real releases create.
- The nightly path required last-green-Core-Tests SHA == HEAD exactly; a
  docs-only or [skip ci] commit at the tip (the release safety-net commit
  itself!) stalled the cron path indefinitely. A green ancestor with no
  package-path changes since is now accepted.
- Replaced the dead 'Bump praisonai' workflow_run filter (those commits no
  longer exist) with 'chore(release):', and wired the previously never-run
  release-gate selftest into the gate as a step.

Workflow (pypi-release.yml):
- New `only` input: a dedicated single-package release is one dropdown pick
  instead of eight skip flags. A "Resolve effective skips" step is the single
  source of truth; the 19 publish/wait conditions are untouched (they gate
  via the seeded pypi_exists outputs).
- PyPI existence probes now distinguish 404 (publish) from outages (fail the
  run before anything is published) instead of treating any error as "not
  published" and then dying on duplicate upload midway.
- The token-refresh step is continue-on-error and its consumer only swaps
  tokens on success -- a failed re-mint previously overwrote a possibly-valid
  token with an empty string, guaranteeing the safety net could not push.
- The safety net now (a) reverts uncommitted wrapper-side rewrites when the
  wrapper release didn't complete, so a wrapper version that never reached
  PyPI is not committed and then skipped past forever, and (b) retries
  rebase+push 3x preferring the version bumps on conflict -- losing that
  commit deadlocks future auto-releases ("already published" forever).
- "Verify PyPI versions" actually probes PyPI now; it previously echoed
  checkmarks derived from input flags for publishes that may never have run.
- Dedicated releases get per-package tags (praisonai-mcp-v0.0.12) since the
  wrapper's "Release v..." tag doesn't exist on that path; only packages
  confirmed on PyPI are tagged.
- uv publish --check-url makes re-uploads idempotent (partial-upload
  recovery); dry-run summary now includes sandbox/deploy and the effective
  skip plan.

Bump script (bump_and_release.py):
- A conflicted `git pull --rebase` previously printed "trying to continue"
  and proceeded from a mid-rebase detached HEAD -- tagging the wrong commit
  and cascading conflict markers into the safety net. It now aborts, retries
  preferring the release commit, and exits before pushing if that fails.
- Commit failures are no longer swallowed (only "nothing to commit" is
  tolerated), so the tag can't silently point at the previous HEAD.
- `gh release create` is idempotent (view-then-create), unblocking reruns
  after a transient failure between push and wrapper publish.
- Tag push is scoped to the release's own tag; `git push --tags -f` could
  force-move historical tags.

Verified: node selftest (10 checks incl. new stale-waiting/dry-run-dedupe
cases), py_compile, YAML parse, actionlint (same 3 pre-existing SC2129
style warnings as main, nothing new), and the rebase-conflict retry logic
exercised against a scratch remote (bump wins, upstream commit preserved).

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
praisonai-triage-agent Bot and others added 28 commits August 9, 2026 16:21
…dbox CLI (fixes MervinPraison#3826)

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…son#3827)

Wrapper BaseFrameworkAdapter._resolve_llm now delegates the spec->model-name
precedence to core's super()._resolve_llm instead of re-implementing it, then
derives base_url/api_key locally to build the PraisonAIModel provider object.
Removes duplicate logic and drift risk. Behaviour-preserving.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…-3824-20260809-1619

fix: reuse image/URL helpers from replay.judge in recipe_optimizer
…-3825-20260809-1619

fix: restore orphaned workflow-runs L3 dashboard page
…-3827-20260809-1620

fix: delegate _resolve_llm model precedence to core
…-3826-20260809-1620

fix: remove superseded unreferenced argparse builders in eval and sandbox CLI
…ection scan (fixes MervinPraison#3836)

- security/injection.py: _extract_strings walks iteratively bounded by bytes+
  cardinality instead of a depth-4 cutoff, scans dict keys, and is cycle-safe so
  nested tool inputs can no longer bypass the scanner.
- db/adapter.py: share one _serialize_tool_call helper across sync/async paths
  that persists the full tool result verbatim (no silent [:1000] truncation).
- persistence/orchestrator.py: add aretrieve_knowledge/aadd_knowledge that
  offload sync stores via asyncio.to_thread (and await native async stores) so
  RAG no longer blocks the event loop.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
The before-tool injection hook allowed a tool call whenever no scanned
string matched. Because _extract_strings bounded the walk by bytes /
cardinality and returned a partial result, an attacker controlling
tool_input could pad benign strings ahead of an injection payload so the
payload was never reached, and the gate allowed the call (Greptile P1).

_extract_strings_bounded now reports whether the walk was truncated, and
create_hook blocks (fail-closed) on truncated, un-vettable input instead
of allowing it. _extract_strings is kept as a back-compat shim.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
…MervinPraison#3838)

Capability helpers (supports_structured_outputs / function_calling /
parallel_function_calling / web_search / prompt_caching) delegated entirely
to litellm and returned False for every model when litellm was absent,
silently disabling structured-output / tool-calling / caching / web paths on
lean provider-native installs. Each helper now falls back to a small,
conservative, pattern-based static heuristic only when litellm is
unavailable. litellm remains authoritative when installed (no behaviour
change for existing deployments); no new deps, no network, no config knobs.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
Address reviewer feedback on PR MervinPraison#3839:
- When litellm is installed but a capability helper is missing or raises,
  preserve litellm as authoritative (return False) instead of overriding
  with the static heuristic. Heuristics now run only when litellm is
  genuinely absent (litellm is None), preventing unsupported params
  (response_format / web_search_options) from reaching provider requests.
- supports_parallel_function_calling now uses a dedicated, narrower
  heuristic instead of the general function-calling one, so serial-only
  tool-calling families are not over-reported as supporting parallel calls.
- Add tests for installed-litellm error path and serial-vs-parallel.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
…-3836-20260810-0829

fix: async RAG offload, non-lossy tool persistence, nesting-proof injection scan
…MervinPraison#3841)

Add a canonical GATEWAY_CONFIG_VERSION stamp and a declarative
LegacyConfigRule contract in core (praisonaiagents/gateway/config.py) with a
single migrate_config_with_doctor executor. Wire `gateway doctor` to detect an
out-of-date config and `--fix` to migrate it forward once and stamp the new
version, so cross-release upgrades no longer silently drift.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…on (fixes MervinPraison#3840)

Per-channel restart budgets are blind to a systemic fault that restarts
every channel at once. Add a pure core FleetSupervisionPolicy breaker
(praisonaiagents/gateway) enforced by the wrapper ChannelHealthMonitor,
which holds restarts and records ONE gateway degraded-owner fact on the
shared DegradedCapabilityRegistry when the fleet thrashes. Wire the
gateway's shared registry into the single supervision owner and expose
gateway.health.fleet_* YAML thresholds.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
…ite (MervinPraison#3841)

Address reviewer findings on PR MervinPraison#3842:

- core: migrate_config_with_doctor now rejects a config stamped by a newer
  build (version > GATEWAY_CONFIG_VERSION) and a malformed/boolean stamp via
  new ConfigVersionError, so an older binary can never downgrade a newer
  config or coerce config_version: true to 1. Exported from gateway package.
- wrapper: doctor guards the migration import (ImportError-safe for older core),
  surfaces the version error as a warning without migrating, and rewrites
  gateway.yaml atomically (tmpfile + fsync + os.replace) so an interrupted
  --fix can't truncate the config. --fix help/docs document config migration.
- tests: newer-version rejection, boolean/non-integer stamp rejection, export.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
…guard (MervinPraison#3840)

- Core: FleetSupervisionPolicy no longer records events while cooling down and
  clears its event window when the cooldown elapses, so the breaker re-arms
  without an external reset() and held (non-)restarts cannot re-trip it.
- Monitor: evaluate the failing-channel fraction on every sweep (note_fleet_state
  now has a live caller) and re-evaluate/clear the breaker inside the monitor
  loop, so the degraded-owner fact clears without a status read.
- Monitor: a disabled per-channel restart budget (max_restarts_per_hour=0) no
  longer counts idle channels as failing.
- Tests: fix CI regression in test_gateway_health_block_matches_runtime_consumer
  (new fleet keys are genuinely consumed by from_dict), add monitor-loop cooldown
  recovery coverage, tighten failing-fraction assertion, correct misleading comment.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
…-3838-20260810-0852

fix: litellm-free capability fallback in model_capabilities
…-3840-20260810-0915

fix: fleet-level crash-loop breaker for gateway channel supervision
…-3841-20260810-0915

fix: gateway config_version stamp + doctor-driven migration
…ixes MervinPraison#3848)

Replace the duplicated public-callable extraction walk in the legacy
recipe/workflow tools.py path with the canonical owner in
praisonai_code.tool_resolver. Adds extract_functions_from_loaded_module
(single authority over the walk) and reuses it from both
ToolResolver.load_functions_from_module and workflow_commands, preserving
the PRAISONAI_ALLOW_LOCAL_TOOLS gate, safe-loader, own-module-origin
filter, and "Loaded N tools" notice.

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
….4.0)

Tenki renamed the PyPI package (tenki-sandbox -> tenki) and dropped the
project concept from the API:
- dep: tenki-sandbox>=0.4.0 -> tenki>=0.5.4; import `from tenki import Client`
- create() no longer takes project_id -> removed all project_id /
  TENKI_PROJECT_ID handling; _resolve_ids simplified to _resolve_workspace
- eliminates the earlier protobuf conflict: tenki 0.5.4 needs protobuf>=5.29.5
  (vs tenki-sandbox's >=6.31), which now co-resolves with autogen-core (5.29.6)
…-3848-20260810-1623

fix: consolidate module-member tool extraction onto canonical helper
- networking: allow_outbound now keys off the real enum — only 'unrestricted'
  gets outbound; 'limited' (also used for --no-networking) disables it. The old
  check compared against a nonexistent 'restricted' value, so outbound was never
  actually disabled.
- get_status/list_instances: a refresh() *exception* is now treated as unknown
  (assume running) rather than STOPPED, so a transient outage no longer hides a
  live, still-billing sandbox. A successful refresh with a non-RUNNING state
  still reports stopped.
- failed-install teardown: terminate() before dropping the local handle, so a
  failed terminate keeps the sandbox tracked instead of leaking it (matches the
  shutdown path).
- don't log raw pip/npm specs (can carry private-index URLs/tokens); log counts.
…KI_AUTH_TOKEN

- managed_local.provision_compute() now forwards `networking` and `metadata`
  into ComputeConfig. Without this they were dropped, so Tenki's allow_outbound
  / tenki_image (the first provider to read them) always saw defaults regardless
  of the caller's request. (Greptile: 'networking policy is dropped')
- TenkiCompute credential resolution now matches the SDK's precedence
  (auth_token -> TENKI_AUTH_TOKEN -> TENKI_API_KEY), so is_available agrees with
  what Client() resolves; docstring + is_available test updated. (CodeRabbit)
…provider

# Conflicts:
#	src/praisonai/praisonai/integrations/managed_local.py
Align the integration-test skip guard with the credential resolution added in
d2fa4df — otherwise the tests skip even when only TENKI_AUTH_TOKEN is set.
(Greptile follow-up)
The upstream merge pulled in .github/workflows changes that aren't part of this
feature; revert them to the fork's versions so the push doesn't require the
workflow OAuth scope. Main's workflows win at merge time and are what run for
the PR anyway.
rishijoshi pushed a commit that referenced this pull request Aug 11, 2026
- Guard Unix-only ``resource`` import so _admission.py loads on Windows;
  sampler self-disables gracefully when unavailable (Greptile P1 #1).
- Wire ``max_rss_mb`` through BotOS + gateway.yaml via a single-knob
  build_memory_pressure_policy() so the feature has a live production
  consumer instead of a dormant param (Greptile P1 MervinPraison#3).
- Surface max_rss_mb in admission stats; document resource-only soft-queue
  degradation honestly rather than silently misleading (Greptile P1 MervinPraison#2).
- Correct MemoryPressurePolicy docstring to reference the real config key.
- Add tests: Windows-safe sampler, single-knob policy, BotOS wiring, stats.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
Undo the earlier fork-workflow revert now that the push has workflow scope, so
the branch's workflows match main again — this removes the 7 stray workflow
files from the PR diff and drops main's pypi-release.yml (a GitGuardian false
positive) back out of our changes.
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.

3 participants