@wardby/cli 0.5.3 → 0.6.0
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.
- package/.env.example +33 -0
- package/README.md +25 -14
- package/dist/claude-coding-worker/context.d.ts +22 -0
- package/dist/claude-coding-worker/context.js +92 -0
- package/dist/claude-coding-worker/driver.d.ts +7 -1
- package/dist/claude-coding-worker/driver.js +38 -5
- package/dist/claude-coding-worker/sdk.js +13 -4
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -2
- package/dist/cli-native-mode.d.ts +9 -0
- package/dist/cli-native-mode.js +20 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +141 -5
- package/dist/coding/claude-context.d.ts +45 -0
- package/dist/coding/claude-context.js +434 -0
- package/dist/coding/profile.d.ts +12 -0
- package/dist/coding/profile.js +6 -0
- package/dist/coding/protocol.d.ts +104 -0
- package/dist/coding/protocol.js +87 -3
- package/dist/coding-worker/artifact.d.ts +8 -0
- package/dist/coding-worker/artifact.js +2 -2
- package/dist/coding-worker/driver.js +2 -0
- package/dist/coding-worker/sdk.d.ts +6 -0
- package/dist/coding-worker/sdk.js +1 -0
- package/dist/coding-worker/skills.d.ts +23 -0
- package/dist/coding-worker/skills.js +117 -0
- package/dist/coding-worker/types.d.ts +2 -0
- package/dist/config/github-allowed-accounts.d.ts +15 -0
- package/dist/config/github-allowed-accounts.js +36 -0
- package/dist/config/low-power.d.ts +12 -0
- package/dist/config/low-power.js +18 -0
- package/dist/config/mobile.d.ts +12 -0
- package/dist/config/mobile.js +18 -0
- package/dist/config/providers.d.ts +53 -0
- package/dist/config/providers.js +92 -6
- package/dist/core/db.d.ts +16 -6
- package/dist/core/db.js +12 -10
- package/dist/core/dispatch.d.ts +11 -0
- package/dist/core/dispatch.js +36 -1
- package/dist/core/engine-native.js +9 -5
- package/dist/core/host-events.d.ts +49 -3
- package/dist/core/host-events.js +289 -15
- package/dist/core/issue-bridge.js +42 -2
- package/dist/core/issue-events.js +12 -1
- package/dist/core/issue-status.js +14 -3
- package/dist/core/merge-order-check.d.ts +129 -0
- package/dist/core/merge-order-check.js +336 -0
- package/dist/core/notification-dispatcher.d.ts +70 -0
- package/dist/core/notification-dispatcher.js +368 -0
- package/dist/core/notification-templates.d.ts +25 -0
- package/dist/core/notification-templates.js +105 -0
- package/dist/core/notifications.d.ts +10 -0
- package/dist/core/notifications.js +38 -0
- package/dist/core/pg-auth.d.ts +22 -0
- package/dist/core/pg-auth.js +152 -0
- package/dist/core/power-composition.d.ts +16 -0
- package/dist/core/power-composition.js +37 -0
- package/dist/core/power-idle.d.ts +14 -0
- package/dist/core/power-idle.js +30 -0
- package/dist/core/power.d.ts +71 -0
- package/dist/core/power.js +202 -0
- package/dist/core/reconciler.d.ts +4 -2
- package/dist/core/reconciler.js +29 -5
- package/dist/core/related-pull-requests.d.ts +48 -4
- package/dist/core/related-pull-requests.js +113 -11
- package/dist/core/repo-access.d.ts +15 -0
- package/dist/core/repo-access.js +66 -0
- package/dist/core/review-fix.d.ts +37 -0
- package/dist/core/review-fix.js +207 -6
- package/dist/core/review-host-tools.d.ts +20 -0
- package/dist/core/review-host-tools.js +58 -2
- package/dist/core/runner.d.ts +336 -3
- package/dist/core/runner.js +894 -317
- package/dist/core/secret-broker-config.d.ts +103 -0
- package/dist/core/secret-broker-config.js +112 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +148 -40
- package/dist/core/workflow-event-recorder.d.ts +14 -0
- package/dist/core/workflow-event-recorder.js +120 -0
- package/dist/core/workflow-events.d.ts +82 -0
- package/dist/core/workflow-events.js +44 -0
- package/dist/core/workflow-run-events.d.ts +16 -0
- package/dist/core/workflow-run-events.js +40 -0
- package/dist/generated/prisma/browser.d.ts +58 -0
- package/dist/generated/prisma/client.d.ts +58 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
- package/dist/generated/prisma/enums.d.ts +27 -0
- package/dist/generated/prisma/enums.js +23 -0
- package/dist/generated/prisma/internal/class.d.ts +88 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
- package/dist/generated/prisma/models/Agent.d.ts +360 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
- package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
- package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
- package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
- package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
- package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
- package/dist/generated/prisma/models/NotificationChannel.js +1 -0
- package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
- package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
- package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
- package/dist/generated/prisma/models/NotificationThread.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +826 -1
- package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
- package/dist/generated/prisma/models/Secret.d.ts +37 -1
- package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
- package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
- package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
- package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
- package/dist/generated/prisma/models.d.ts +8 -0
- package/dist/help/build.js +4 -2
- package/dist/help/catalog.d.ts +1 -1
- package/dist/help/catalog.js +31 -6
- package/dist/help-index.json +1363 -176
- package/dist/import/create.js +17 -0
- package/dist/import/neutral-schema.d.ts +6 -2
- package/dist/import/neutral-schema.js +2 -0
- package/dist/mcp/auth/access.d.ts +1 -0
- package/dist/mcp/auth/repo-authorization.js +14 -0
- package/dist/mcp/auth/resource-server.d.ts +2 -1
- package/dist/mcp/auth/resource-server.js +7 -2
- package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/browser.js +19 -6
- package/dist/mcp/context.d.ts +6 -0
- package/dist/mcp/host-events/github-ingress.d.ts +23 -1
- package/dist/mcp/host-events/github-ingress.js +68 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +59 -10
- package/dist/mcp/tools/agents.js +82 -11
- package/dist/mcp/tools/notification-channels.d.ts +2 -0
- package/dist/mcp/tools/notification-channels.js +293 -0
- package/dist/mcp/tools/power.d.ts +2 -0
- package/dist/mcp/tools/power.js +77 -0
- package/dist/mcp/tools/runs.d.ts +0 -6
- package/dist/mcp/tools/runs.js +7 -2
- package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
- package/dist/mcp/tools/secret-broker-compat.js +23 -0
- package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
- package/dist/mcp/tools/secret-elicitation-form.js +200 -10
- package/dist/mcp/tools/secret-elicitation-server.js +8 -1
- package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
- package/dist/mcp/tools/secret-elicitation.js +110 -12
- package/dist/mcp/tools/secrets.js +184 -7
- package/dist/mcp/tools/tools.d.ts +1 -1
- package/dist/mcp/tools/tools.js +22 -2
- package/dist/native-worker/composition.d.ts +23 -0
- package/dist/native-worker/composition.js +77 -0
- package/dist/native-worker/docker-isolation.d.ts +65 -0
- package/dist/native-worker/docker-isolation.js +183 -0
- package/dist/native-worker/docker-launcher.d.ts +74 -0
- package/dist/native-worker/docker-launcher.js +237 -0
- package/dist/native-worker/gateway-server.d.ts +39 -0
- package/dist/native-worker/gateway-server.js +315 -0
- package/dist/native-worker/gateway.d.ts +44 -0
- package/dist/native-worker/gateway.js +197 -0
- package/dist/native-worker/http-server.d.ts +22 -0
- package/dist/native-worker/http-server.js +109 -0
- package/dist/native-worker/http-transport.d.ts +17 -0
- package/dist/native-worker/http-transport.js +88 -0
- package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
- package/dist/native-worker/kubernetes-isolation.js +218 -0
- package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
- package/dist/native-worker/kubernetes-launcher.js +335 -0
- package/dist/native-worker/launch.d.ts +45 -0
- package/dist/native-worker/launch.js +44 -0
- package/dist/native-worker/ledger.d.ts +109 -0
- package/dist/native-worker/ledger.js +192 -0
- package/dist/native-worker/loopback.d.ts +12 -0
- package/dist/native-worker/loopback.js +49 -0
- package/dist/native-worker/main.d.ts +7 -0
- package/dist/native-worker/main.js +74 -0
- package/dist/native-worker/protocol.d.ts +744 -0
- package/dist/native-worker/protocol.js +200 -0
- package/dist/native-worker/sandbox-executor.d.ts +68 -0
- package/dist/native-worker/sandbox-executor.js +210 -0
- package/dist/native-worker/stdio.d.ts +29 -0
- package/dist/native-worker/stdio.js +188 -0
- package/dist/native-worker/warm-delivery.d.ts +16 -0
- package/dist/native-worker/warm-delivery.js +33 -0
- package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
- package/dist/native-worker/warm-pool-ledger.js +73 -0
- package/dist/native-worker/warm-pool.d.ts +107 -0
- package/dist/native-worker/warm-pool.js +283 -0
- package/dist/native-worker/worker.d.ts +17 -0
- package/dist/native-worker/worker.js +115 -0
- package/dist/providers/auth/authorization-server.d.ts +3 -0
- package/dist/providers/auth/index.js +2 -0
- package/dist/providers/auth/self-hosted.d.ts +13 -1
- package/dist/providers/auth/self-hosted.js +54 -4
- package/dist/providers/chat/fake.d.ts +46 -0
- package/dist/providers/chat/fake.js +39 -0
- package/dist/providers/chat/index.d.ts +4 -0
- package/dist/providers/chat/index.js +8 -0
- package/dist/providers/chat/slack.d.ts +37 -0
- package/dist/providers/chat/slack.js +105 -0
- package/dist/providers/chat/types.d.ts +42 -0
- package/dist/providers/chat/types.js +19 -0
- package/dist/providers/engine/types.d.ts +19 -0
- package/dist/providers/engine/types.js +22 -0
- package/dist/providers/executor/composition.d.ts +24 -1
- package/dist/providers/executor/composition.js +39 -6
- package/dist/providers/executor/container.d.ts +24 -1
- package/dist/providers/executor/container.js +101 -11
- package/dist/providers/executor/dbos.d.ts +42 -0
- package/dist/providers/executor/dbos.js +118 -12
- package/dist/providers/executor/deferred.d.ts +56 -0
- package/dist/providers/executor/deferred.js +119 -0
- package/dist/providers/executor/routing.d.ts +37 -10
- package/dist/providers/executor/routing.js +73 -32
- package/dist/providers/executor/types.d.ts +12 -0
- package/dist/providers/jobs/docker.js +3 -3
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
- package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
- package/dist/providers/jobs/kubernetes-api.js +7 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
- package/dist/providers/jobs/kubernetes-client.js +52 -3
- package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
- package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
- package/dist/providers/jobs/kubernetes-isolation.js +6 -0
- package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
- package/dist/providers/jobs/kubernetes-platform.js +38 -1
- package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
- package/dist/providers/jobs/kubernetes-preflight.js +4 -2
- package/dist/providers/jobs/kubernetes.d.ts +36 -1
- package/dist/providers/jobs/kubernetes.js +74 -4
- package/dist/providers/jobs/metadata-probes.d.ts +11 -0
- package/dist/providers/jobs/metadata-probes.js +13 -0
- package/dist/providers/jobs/types.d.ts +9 -0
- package/dist/providers/review-host/github.d.ts +11 -1
- package/dist/providers/review-host/github.js +50 -0
- package/dist/providers/review-host/types.d.ts +23 -0
- package/dist/providers/vcs/github.d.ts +32 -6
- package/dist/providers/vcs/github.js +74 -16
- package/dist/quickstart/coding-seed.d.ts +10 -3
- package/dist/quickstart/coding-seed.js +74 -3
- package/dist/quickstart/images.d.ts +2 -0
- package/dist/quickstart/images.js +7 -1
- package/dist/quickstart/reviewer-prompt.d.ts +1 -1
- package/dist/quickstart/reviewer-prompt.js +1 -1
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/brokered-fetch.d.ts +23 -0
- package/dist/sandbox/brokered-fetch.js +37 -0
- package/dist/sandbox/host-functions.d.ts +34 -0
- package/dist/sandbox/host-functions.js +144 -80
- package/dist/sandbox/prelude.js +7 -1
- package/dist/sandbox/run-in-sandbox.d.ts +19 -0
- package/dist/sandbox/run-in-sandbox.js +28 -10
- package/dist/sandbox/safe-fetch.d.ts +2 -0
- package/dist/sandbox/safe-fetch.js +3 -1
- package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
- package/dist/sandbox/secret-broker-sigv4.js +49 -0
- package/dist/sandbox/secret-broker.d.ts +34 -0
- package/dist/sandbox/secret-broker.js +162 -0
- package/dist/sandbox/user-tool.d.ts +14 -0
- package/dist/sandbox/user-tool.js +32 -0
- package/dist/serve.js +18 -0
- package/dist/viewer/api-schema.d.ts +210 -14
- package/dist/viewer/api-schema.js +22 -0
- package/dist/viewer/build-schemas.js +4 -1
- package/dist/viewer/event-bus.d.ts +6 -0
- package/dist/viewer/event-bus.js +16 -7
- package/dist/viewer/graph.d.ts +5 -2
- package/dist/viewer/graph.js +7 -2
- package/dist/viewer/http.d.ts +0 -1
- package/dist/viewer/http.js +2 -2
- package/dist/viewer/infra.js +25 -2
- package/dist/viewer/limits.d.ts +4 -0
- package/dist/viewer/limits.js +4 -0
- package/dist/viewer/openapi.d.ts +15 -0
- package/dist/viewer/openapi.js +172 -0
- package/docs/README.md +7 -0
- package/docs/agent-recipes.md +57 -8
- package/docs/architecture-runtime.md +16 -1
- package/docs/code-review-agents.md +139 -5
- package/docs/coding-agent-setup.md +113 -5
- package/docs/coding-packages.md +4 -2
- package/docs/coding-services.md +3 -1
- package/docs/coding-worker-byo-images.md +198 -6
- package/docs/coding-worker-isolation.md +68 -14
- package/docs/getting-started-gke.md +225 -9
- package/docs/getting-started-identity-provider.md +21 -10
- package/docs/getting-started.md +5 -4
- package/docs/jira-agents.md +5 -1
- package/docs/native-sandbox.md +635 -0
- package/docs/release-verification.md +9 -0
- package/docs/security-deployment.md +113 -12
- package/docs/slack-notifications.md +252 -0
- package/docs/tool-secrets.md +252 -0
- package/docs/viewer-api.md +46 -5
- package/docs/viewer-api.openapi.yml +1404 -0
- package/help/admin-viewer.md +6 -2
- package/help/agent-recipes.md +27 -0
- package/help/architecture-agent.md +1 -1
- package/help/brokered-secrets.md +79 -0
- package/help/build-claude-code-image.md +131 -0
- package/help/build-worker-image.md +3 -6
- package/help/code-review-agents.md +68 -6
- package/help/deploy-gke.md +8 -1
- package/help/deployment-targets.md +25 -14
- package/help/errors/coding-provider-not-configured.md +2 -2
- package/help/errors/database-auth-invalid.md +28 -0
- package/help/errors/github-account-not-allowed.md +25 -0
- package/help/errors/low-power-disabled.md +29 -0
- package/help/errors/low-power-scale-forbidden.md +35 -0
- package/help/errors/low-power-wake-timeout.md +30 -0
- package/help/errors/mobile-disabled.md +31 -0
- package/help/errors/native-sandbox-capacity.md +22 -0
- package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
- package/help/errors/native-sandbox-docker-failed.md +20 -0
- package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
- package/help/errors/native-sandbox-image-not-pinned.md +19 -0
- package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
- package/help/errors/native-sandbox-network-unenforced.md +22 -0
- package/help/errors/native-sandbox-requires-catalog.md +31 -0
- package/help/errors/native-sandbox-unavailable.md +19 -0
- package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
- package/help/errors/native-sandbox-worker-exited.md +20 -0
- package/help/errors/native-sandbox-worker-lost.md +20 -0
- package/help/errors/native-sandbox-worker-unready.md +22 -0
- package/help/errors/secret-broker-body-invalid.md +26 -0
- package/help/errors/secret-broker-changed.md +20 -0
- package/help/errors/secret-broker-config-invalid.md +30 -0
- package/help/errors/secret-broker-conflict.md +27 -0
- package/help/errors/secret-broker-destination-denied.md +28 -0
- package/help/errors/secret-broker-value-invalid.md +28 -0
- package/help/errors/secret-brokered.md +24 -0
- package/help/errors/secret-not-brokered.md +25 -0
- package/help/errors/slack-auth-failed.md +48 -0
- package/help/errors/slack-channel-unreachable.md +39 -0
- package/help/errors/slack-not-configured.md +33 -0
- package/help/getting-started.md +4 -5
- package/help/github.md +18 -1
- package/help/identity-and-access.md +5 -1
- package/help/local-repositories.md +2 -3
- package/help/low-power-mode.md +125 -0
- package/help/merge-order-check.md +117 -0
- package/help/mobile-app.md +54 -0
- package/help/native-sandbox.md +136 -0
- package/help/observability.md +8 -0
- package/help/related-pull-requests.md +70 -9
- package/help/repo-instructions-and-skills.md +91 -0
- package/help/review-fix-rounds.md +10 -0
- package/help/slack-notifications.md +62 -0
- package/help/troubleshooting/coding-workers.md +21 -0
- package/package.json +11 -4
- package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
- package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
- package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
- package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
- package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
- package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
- package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
- package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
- package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
- package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
- package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
- package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
- package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
- package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
- package/prisma/schema.prisma +362 -91
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# Native sandbox
|
|
2
|
+
|
|
3
|
+
> **Requires Wardby 0.5.4 or later.**
|
|
4
|
+
|
|
5
|
+
By default a native agent runs inside the Wardby server process: its turn loop
|
|
6
|
+
and any user tool code you attach execute next to the database connection, the
|
|
7
|
+
LLM credentials, and every other agent's runs. **Sandbox mode** moves the whole
|
|
8
|
+
turn loop and the agent's user tools into a single-use worker that holds no
|
|
9
|
+
credentials, no database access, no container-runtime access, and no network
|
|
10
|
+
except one path to a separate **native gateway**. The worker is a Docker
|
|
11
|
+
container or, on a Kubernetes cluster, a pod; you choose with
|
|
12
|
+
`NATIVE_SANDBOX_LAUNCHER`.
|
|
13
|
+
|
|
14
|
+
Use it when a native agent runs tool code or prompts you do not fully trust, for
|
|
15
|
+
example tools written by people other than the operators, or agents that fetch
|
|
16
|
+
and process untrusted content. Leave agents in the default `control-plane` mode
|
|
17
|
+
when you trust the tools and want the simplest deployment.
|
|
18
|
+
|
|
19
|
+
Sandbox mode applies to native agents only. Coding agents have their own
|
|
20
|
+
isolation model; see [Coding-worker isolation](coding-worker-isolation.md).
|
|
21
|
+
|
|
22
|
+
## What is isolated and what is not
|
|
23
|
+
|
|
24
|
+
| Component | Trusted? | Where it runs |
|
|
25
|
+
| ------------------------- | -------- | ---------------------------------------- |
|
|
26
|
+
| Wardby server / scheduler | Yes | Your host, VM, or cluster |
|
|
27
|
+
| Native gateway | Yes | A separate container or Deployment |
|
|
28
|
+
| Postgres, LLM provider | Yes | Reachable only from server/gateway |
|
|
29
|
+
| Agent turn loop (worker) | No | Single-use container or pod, one per run |
|
|
30
|
+
| User tool code (worker) | No | The same worker |
|
|
31
|
+
|
|
32
|
+
The Docker worker container:
|
|
33
|
+
|
|
34
|
+
- has no LLM keys, secrets, database URL, or Docker socket, and receives only a
|
|
35
|
+
run-scoped capability on its standard input (never in its environment,
|
|
36
|
+
arguments, labels, or `docker inspect` output);
|
|
37
|
+
- runs as non-root (uid 10001) on a read-only root filesystem with a
|
|
38
|
+
size-capped `/tmp` (no exec), all Linux capabilities dropped,
|
|
39
|
+
`no-new-privileges`, and CPU, memory, and PID limits;
|
|
40
|
+
- sits on an internal Docker network created for that run alone, shared only
|
|
41
|
+
with the gateway container, with no route to the internet, the host, or any
|
|
42
|
+
other run.
|
|
43
|
+
|
|
44
|
+
On Kubernetes the worker is a pod with the same posture; see
|
|
45
|
+
[Set up with Kubernetes](#set-up-with-kubernetes) for what the pod and its
|
|
46
|
+
NetworkPolicy contain and how the isolation is proven before the run starts.
|
|
47
|
+
|
|
48
|
+
Everything the worker needs from the outside world (model calls, the built-in
|
|
49
|
+
tools, and tool access to datastores, secrets, and outbound fetch) goes through
|
|
50
|
+
the gateway, which enforces each tool attachment's grants. A worker can only
|
|
51
|
+
ask for what the agent's attachments already allow.
|
|
52
|
+
|
|
53
|
+
Containers are defense in depth, not a virtual-machine boundary. On Kubernetes,
|
|
54
|
+
set a sandboxing runtime class such as gVisor where you have one. Run the Docker
|
|
55
|
+
host on a machine that holds no production credentials beyond what the server
|
|
56
|
+
and gateway need.
|
|
57
|
+
|
|
58
|
+
## Topology (Docker)
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Docker host
|
|
62
|
+
+---------------------------------------------------------+
|
|
63
|
+
| wardby serve (server + scheduler) |
|
|
64
|
+
| | docker run / network create / network connect |
|
|
65
|
+
| v |
|
|
66
|
+
| per-run internal network (no external route) |
|
|
67
|
+
| +--------------------+ +--------------------+ |
|
|
68
|
+
| | native worker | HTTP | native gateway | |
|
|
69
|
+
| | (no credentials) +------->| wardby-native- |--+--> LLM provider
|
|
70
|
+
| | single-use | | gateway :8790 |--+--> Postgres
|
|
71
|
+
| +--------------------+ | (credentials, DB) | |
|
|
72
|
+
| +--------------------+ |
|
|
73
|
+
+---------------------------------------------------------+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For each run the server creates a new internal network, connects the gateway
|
|
77
|
+
container to it under the alias `wardby-native-gateway`, and starts the worker
|
|
78
|
+
on it. When the run ends the worker, the network, and the gateway's membership
|
|
79
|
+
of it are removed.
|
|
80
|
+
|
|
81
|
+
The gateway is stateless: all state is in Postgres. You can restart it while
|
|
82
|
+
runs are in flight; workers retry their calls. It must not be given the Docker
|
|
83
|
+
socket, because workers can reach it.
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- **Docker launcher:** Docker on the host that runs `wardby serve` (or
|
|
88
|
+
`wardby scheduler`). The server launches workers with the `docker` CLI, so its
|
|
89
|
+
user must be able to use the Docker daemon. A running native gateway container
|
|
90
|
+
(below) on the same Docker host, with access to the same Postgres database as
|
|
91
|
+
the server.
|
|
92
|
+
- **Kubernetes launcher:** a cluster whose CNI enforces `NetworkPolicy`, a
|
|
93
|
+
namespace holding the native gateway, and a server identity with the
|
|
94
|
+
permissions in [Server permissions](#server-permissions).
|
|
95
|
+
- The `wardby-native-worker` image, pinned by digest (Docker also accepts a
|
|
96
|
+
local image id; Kubernetes requires a registry digest).
|
|
97
|
+
- A running `wardby serve` or `wardby scheduler`. Sub-agents of a sandboxed run
|
|
98
|
+
are started by the scheduler leader (see [Sub-agents](#sub-agents)).
|
|
99
|
+
|
|
100
|
+
## Set up with Docker
|
|
101
|
+
|
|
102
|
+
### 1. Choose the worker image
|
|
103
|
+
|
|
104
|
+
Releases publish the image to the GitHub Container Registry as
|
|
105
|
+
`ghcr.io/<org>/wardby-native-worker`. The exact digest-pinned reference for the
|
|
106
|
+
installed version is listed as `nativeWorker` in `dist/quickstart-images.json`
|
|
107
|
+
inside the npm package. Use that reference as-is:
|
|
108
|
+
|
|
109
|
+
```dotenv
|
|
110
|
+
NATIVE_SANDBOX_WORKER_IMAGE=ghcr.io/your-org/wardby-native-worker@sha256:replace-with-digest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The image must be immutable: a `repo@sha256:...` digest or a local image id.
|
|
114
|
+
A mutable tag such as `:latest` is refused with
|
|
115
|
+
[`native_sandbox_image_not_pinned`](../help/errors/native-sandbox-image-not-pinned.md).
|
|
116
|
+
|
|
117
|
+
To build it yourself from a repository checkout:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run native-worker:image:local
|
|
121
|
+
docker image inspect --format '{{.Id}}' wardby-native-worker:local
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Use the printed `sha256:...` image id as `NATIVE_SANDBOX_WORKER_IMAGE`. A local
|
|
125
|
+
id must already exist on the Docker host; a digest reference is pulled once if
|
|
126
|
+
missing.
|
|
127
|
+
|
|
128
|
+
### 2. Run the native gateway
|
|
129
|
+
|
|
130
|
+
The gateway is the same Wardby build as the server, started with the
|
|
131
|
+
`native-gateway` command. It needs the server's database, LLM, secrets, and
|
|
132
|
+
integration settings, and **no Docker access**.
|
|
133
|
+
|
|
134
|
+
| Gateway variable | Purpose |
|
|
135
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| `NATIVE_GATEWAY_LISTEN` | `host:port` to listen on. Default `0.0.0.0:8790`. |
|
|
137
|
+
| `DATABASE_URL` | The same database the server uses. With `DATABASE_AUTH=rds-iam` the gateway also needs `DATABASE_AUTH`, `AWS_REGION` (or `DATABASE_AWS_REGION`) and the CA bundle file named by `sslrootcert` ([details](security-deployment.md#database-authentication)). |
|
|
138
|
+
| LLM provider keys | The same provider settings as the server (for example `OPENAI_API_KEY`). |
|
|
139
|
+
| `SECRET_APP_KEY` | Same value as the server, so tools can read their bound secrets or send brokered ones ([details](tool-secrets.md)). |
|
|
140
|
+
| Integration settings | The same repository-host and issue-tracker settings as the server. |
|
|
141
|
+
|
|
142
|
+
If sandboxed agents delegate to coding agents, give the gateway the same
|
|
143
|
+
coding image settings as the server (for example `CODING_WORKER_IMAGE`); the
|
|
144
|
+
gateway only resolves them, it never starts coding workers.
|
|
145
|
+
|
|
146
|
+
For local development, the repository ships a compose overlay
|
|
147
|
+
(`deploy/local/docker-compose.native-sandbox.yml`) that runs the gateway next to
|
|
148
|
+
the local Postgres from `deploy/local/docker-compose.yml`:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
docker compose -f deploy/local/docker-compose.yml -f deploy/local/docker-compose.native-sandbox.yml --env-file .env.local up -d --build native-gateway
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The overlay names the container `wardby-native-gateway`, passes the database
|
|
155
|
+
URL for the compose network and your LLM keys from the environment file. It
|
|
156
|
+
runs with a read-only root filesystem and all capabilities dropped, and mounts
|
|
157
|
+
no Docker socket.
|
|
158
|
+
It defines its own health check against `/healthz`.
|
|
159
|
+
|
|
160
|
+
For other deployments, run `wardby native-gateway` in a container built from
|
|
161
|
+
the same image as the server, on the same Docker host, with any container
|
|
162
|
+
name you choose, and give that name to the server in the next step. Do not
|
|
163
|
+
publish its port to the internet; only the per-run networks and your own
|
|
164
|
+
health checks need to reach it.
|
|
165
|
+
|
|
166
|
+
### 3. Configure the server
|
|
167
|
+
|
|
168
|
+
Add these to the server's environment (`wardby serve` or `wardby scheduler`),
|
|
169
|
+
then restart it:
|
|
170
|
+
|
|
171
|
+
```dotenv
|
|
172
|
+
NATIVE_SANDBOX_LAUNCHER=docker
|
|
173
|
+
NATIVE_SANDBOX_WORKER_IMAGE=ghcr.io/your-org/wardby-native-worker@sha256:replace-with-digest
|
|
174
|
+
NATIVE_GATEWAY_CONTAINER=wardby-native-gateway
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [Configuration reference](#configuration-reference) for every variable,
|
|
178
|
+
including the Kubernetes ones. A missing required variable, or a launcher other
|
|
179
|
+
than `docker` or `kubernetes`, stops the server at startup with a message naming the variable. When
|
|
180
|
+
`NATIVE_SANDBOX_LAUNCHER` is unset, sandbox mode is off and any sandbox-mode
|
|
181
|
+
run fails closed (see [Failure modes](#failure-modes)).
|
|
182
|
+
|
|
183
|
+
`JOB_LAUNCHER` (coding agents) is a separate setting; you can enable either,
|
|
184
|
+
both, or neither.
|
|
185
|
+
|
|
186
|
+
### 4. Set an agent's mode
|
|
187
|
+
|
|
188
|
+
Each native agent has a `nativeExecutionMode` setting:
|
|
189
|
+
|
|
190
|
+
- `control-plane` (default): the run executes in the server process.
|
|
191
|
+
- `sandbox`: the run executes in a worker container as described above.
|
|
192
|
+
|
|
193
|
+
Set it with the MCP tools `create_agent` or `update_agent` (field
|
|
194
|
+
`nativeExecutionMode`, native agents only), or in an import bundle. Setting
|
|
195
|
+
`sandbox` is refused while the server has no native sandbox configured.
|
|
196
|
+
|
|
197
|
+
From the command line, with the same environment as the server:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
wardby agent create --name <n> --model <m> --prompt <p> --budget <usd> --native-execution-mode sandbox
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
wardby agent mode <name> sandbox
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The CLI refuses `sandbox` with `native_sandbox_unavailable` when its own
|
|
208
|
+
environment has no `NATIVE_SANDBOX_LAUNCHER`, so run it where the server's
|
|
209
|
+
sandbox variables are set. `wardby agent list` marks sandbox-mode agents.
|
|
210
|
+
`get_run` and `list_runs` report each run's mode as `nativeExecutionMode`
|
|
211
|
+
(`control-plane`, `sandbox`, or `null` for a coding run).
|
|
212
|
+
|
|
213
|
+
Each run keeps the mode it started with. Changing an agent affects only the
|
|
214
|
+
runs that start afterwards.
|
|
215
|
+
|
|
216
|
+
## Set up with Kubernetes
|
|
217
|
+
|
|
218
|
+
With `NATIVE_SANDBOX_LAUNCHER=kubernetes` the server starts one pod per
|
|
219
|
+
sandbox run in a namespace you choose and deletes it when the run ends. The
|
|
220
|
+
native gateway runs in the same namespace as an ordinary Deployment.
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
cluster namespace
|
|
224
|
+
+---------------------------------------------------------------+
|
|
225
|
+
| wardby serve --create pod / Secret / NetworkPolicy--> |
|
|
226
|
+
| |
|
|
227
|
+
| native-run pod (no credentials) native gateway pods |
|
|
228
|
+
| +---------------------------+ 8790 +-------------------+ |
|
|
229
|
+
| | worker, single-use +------->| wardby-native- |--+--> LLM provider
|
|
230
|
+
| | egress: gateway:8790 only | x | gateway Service |--+--> Postgres
|
|
231
|
+
| +---------------------------+ 8791 +-------------------+ |
|
|
232
|
+
+---------------------------------------------------------------+
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 1. Choose the worker image
|
|
236
|
+
|
|
237
|
+
Use the digest-pinned `nativeWorker` reference from
|
|
238
|
+
`dist/quickstart-images.json`, or push your own build to a registry your
|
|
239
|
+
cluster can pull from. The reference must be a registry digest,
|
|
240
|
+
`repo@sha256:...`. A local image id means nothing to a cluster, and a
|
|
241
|
+
mutable tag is refused; both stop the server at startup or fail the run with
|
|
242
|
+
[`native_sandbox_image_not_pinned`](../help/errors/native-sandbox-image-not-pinned.md).
|
|
243
|
+
|
|
244
|
+
### 2. Deploy the native gateway
|
|
245
|
+
|
|
246
|
+
Run `wardby native-gateway` as a Deployment in the run namespace, from the
|
|
247
|
+
same Wardby image as the server. Requirements:
|
|
248
|
+
|
|
249
|
+
- **Environment:** the server's `DATABASE_URL`, `SECRET_APP_KEY` (so tools can
|
|
250
|
+
read their bound secrets or send brokered ones; see
|
|
251
|
+
[tool-secrets.md](tool-secrets.md)), the LLM provider keys, and any integration or
|
|
252
|
+
coding image settings the server has (see the gateway table in the Docker
|
|
253
|
+
setup above). Also `NATIVE_GATEWAY_LISTEN` (default `0.0.0.0:8790`) and
|
|
254
|
+
`NATIVE_GATEWAY_DENY_PORT` (default `8791`). Keep these in a Secret.
|
|
255
|
+
- **No Kubernetes access:** `automountServiceAccountToken: false` and no Role
|
|
256
|
+
or RoleBinding. Workers can reach the gateway, so it must not be able to
|
|
257
|
+
touch the cluster. A read-only root filesystem with a small `/tmp`,
|
|
258
|
+
all capabilities dropped, and a non-root user all apply.
|
|
259
|
+
- **Pod label:** `app.kubernetes.io/name: wardby-native-gateway`. The per-run
|
|
260
|
+
NetworkPolicy selects the gateway pods by this label.
|
|
261
|
+
- **A ClusterIP Service** named `wardby-native-gateway` (or set
|
|
262
|
+
`NATIVE_GATEWAY_SERVICE`) exposing **both** port 8790 and the deny port 8791. Nothing is served on 8791; it exists only so isolation can be proven.
|
|
263
|
+
- **A NetworkPolicy on the gateway pods** that admits pods labeled
|
|
264
|
+
`wardby.io/component: native-run` on **both 8790 and 8791**, with egress to
|
|
265
|
+
cluster DNS, the model providers (HTTPS), and the database. Do not drop the
|
|
266
|
+
8791 rule: the check that a worker cannot reach 8791 only means something if
|
|
267
|
+
8791 is open at the destination, so that the worker's own policy is the only
|
|
268
|
+
thing that can block it. Without it, a worker whose policy was never
|
|
269
|
+
programmed would read the port as blocked and its isolation could be
|
|
270
|
+
wrongly proven.
|
|
271
|
+
- Replicas are fine; the gateway is stateless.
|
|
272
|
+
|
|
273
|
+
The gateway ships in every target's manifests: the Deployment, Service, and
|
|
274
|
+
NetworkPolicy are in `deploy/kind-coding/manifests/base/native-gateway.yaml`,
|
|
275
|
+
and each overlay (`kind`, `gke-autopilot`) supplies its own database egress and
|
|
276
|
+
the `wardby-native-gateway-env` Secret. On a cluster you manage yourself, copy
|
|
277
|
+
that file and adapt it. On GKE Autopilot the gateway also gets its own Workload
|
|
278
|
+
Identity and Cloud SQL database user; see
|
|
279
|
+
[Getting started on GKE](getting-started-gke.md#native-sandbox).
|
|
280
|
+
|
|
281
|
+
### 3. Server permissions
|
|
282
|
+
|
|
283
|
+
The server (`wardby serve` or `wardby scheduler`) needs a Role in the run
|
|
284
|
+
namespace that allows:
|
|
285
|
+
|
|
286
|
+
| Resource | Verbs |
|
|
287
|
+
| ----------------------------------- | --------------------------------- |
|
|
288
|
+
| `pods` | `create`, `get`, `delete`, `list` |
|
|
289
|
+
| `pods/exec` | `create`, `get` |
|
|
290
|
+
| `secrets` | `create`, `delete` |
|
|
291
|
+
| `networking.k8s.io/networkpolicies` | `create`, `get`, `delete` |
|
|
292
|
+
| `services` | `get` |
|
|
293
|
+
|
|
294
|
+
`list` on pods lets the startup sweep find leftover worker pods by label;
|
|
295
|
+
`pods/exec` is how the server proves isolation from inside the pod; `services`
|
|
296
|
+
is read once to find the gateway's ClusterIP. Coding runs on Kubernetes use a
|
|
297
|
+
largely overlapping Role (`deploy/kind-coding/manifests/base/launcher-role.yaml`).
|
|
298
|
+
|
|
299
|
+
### 4. Configure the server
|
|
300
|
+
|
|
301
|
+
```dotenv
|
|
302
|
+
NATIVE_SANDBOX_LAUNCHER=kubernetes
|
|
303
|
+
NATIVE_SANDBOX_WORKER_IMAGE=registry.example.com/wardby-native-worker@sha256:replace-with-digest
|
|
304
|
+
NATIVE_SANDBOX_NAMESPACE=wardby
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Optional: `KUBERNETES_CONTEXT` (the kubeconfig context; unset uses the
|
|
308
|
+
in-cluster identity or the current context), `NATIVE_SANDBOX_RUNTIME_CLASS`
|
|
309
|
+
(for example a gVisor class), `NATIVE_GATEWAY_SERVICE`, the CPU and memory
|
|
310
|
+
limits, `NATIVE_SANDBOX_MAX_CONCURRENT`, `NATIVE_SANDBOX_PRIORITY_CLASS`, and
|
|
311
|
+
the two start-up time limits. See [Configuration reference](#configuration-reference). Then set
|
|
312
|
+
agents to `sandbox` mode as described in
|
|
313
|
+
[Set an agent's mode](#4-set-an-agents-mode).
|
|
314
|
+
|
|
315
|
+
### What each run gets
|
|
316
|
+
|
|
317
|
+
For every run the server creates, then deletes when the run ends:
|
|
318
|
+
|
|
319
|
+
- A **Secret** holding the run's input and capability, immutable and mounted
|
|
320
|
+
read-only into the pod. The capability is never in the pod's environment,
|
|
321
|
+
arguments, or labels.
|
|
322
|
+
- A **NetworkPolicy** selecting only that pod: no ingress, and egress only to
|
|
323
|
+
the gateway pods on port 8790.
|
|
324
|
+
- The **pod**, with no service-account token, non-root (uid and fsGroup
|
|
325
|
+
10001), a read-only root filesystem with a 64 MiB `/tmp`, all capabilities
|
|
326
|
+
dropped, no privilege escalation, the runtime-default seccomp profile, CPU
|
|
327
|
+
and memory requests equal to limits, an optional priority class
|
|
328
|
+
(`NATIVE_SANDBOX_PRIORITY_CLASS`), and `activeDeadlineSeconds` set to the
|
|
329
|
+
run's deadline. Kubernetes has no per-pod process limit, so
|
|
330
|
+
`NATIVE_SANDBOX_PIDS` does not apply.
|
|
331
|
+
|
|
332
|
+
Workers dial the gateway Service's ClusterIP, so they need no DNS egress. Set
|
|
333
|
+
`NATIVE_GATEWAY_URL` only to override that.
|
|
334
|
+
|
|
335
|
+
### How isolation is proven
|
|
336
|
+
|
|
337
|
+
A NetworkPolicy takes effect a few seconds after its pod starts, so the run
|
|
338
|
+
does not begin until the server has proven the isolation. A new sandbox run
|
|
339
|
+
starts **not ready**: the gateway refuses every call from it with a
|
|
340
|
+
`not_ready` response, and the worker keeps retrying. Meanwhile the server:
|
|
341
|
+
|
|
342
|
+
1. Reads back the pod and NetworkPolicy it created and compares them with what
|
|
343
|
+
it built. If an admission controller or mutating webhook changed the
|
|
344
|
+
security-relevant parts (service-account token, host networking, runtime
|
|
345
|
+
class, priority class, image, container resources and security context,
|
|
346
|
+
extra containers, or the policy),
|
|
347
|
+
the run fails with
|
|
348
|
+
[`native_sandbox_isolation_mismatch`](../help/errors/native-sandbox-isolation-mismatch.md).
|
|
349
|
+
2. Waits for the pod to run, then executes a short probe inside it. The probe
|
|
350
|
+
must reach the gateway on 8790, must fail to reach the gateway's deny port
|
|
351
|
+
8791 at the same address, and must fail to reach an outside address or any
|
|
352
|
+
cloud metadata endpoint: `169.254.169.254:80`, the EKS Pod Identity agent
|
|
353
|
+
(`169.254.170.23:80` and its IPv6 address `[fd00:ec2::23]:80`) and EC2's IPv6
|
|
354
|
+
metadata address `[fd00:ec2::254]:80`.
|
|
355
|
+
3. Only when the probe passes does it mark the run ready. The gateway then
|
|
356
|
+
accepts the worker's calls.
|
|
357
|
+
|
|
358
|
+
If the probe cannot pass within its time limit the run fails with
|
|
359
|
+
[`native_sandbox_network_unenforced`](../help/errors/native-sandbox-network-unenforced.md)
|
|
360
|
+
and the pod is removed. What operators see while this happens: a run that
|
|
361
|
+
stays in its starting state for a few seconds (longer on a cold image pull),
|
|
362
|
+
and nothing in the model or tool logs until it is ready. A cluster whose CNI
|
|
363
|
+
does not enforce NetworkPolicy fails every sandbox run this way, by design.
|
|
364
|
+
|
|
365
|
+
### Managed Kubernetes platforms
|
|
366
|
+
|
|
367
|
+
Set `KUBERNETES_PLATFORM=eks` on Amazon EKS clusters that have a gVisor node
|
|
368
|
+
group. It requires the same gVisor runtime class as `gke-autopilot` (the server
|
|
369
|
+
refuses to start unless `NATIVE_SANDBOX_RUNTIME_CLASS` or
|
|
370
|
+
`KUBERNETES_RUNTIME_CLASS` is `gvisor`) and expects a RuntimeClass `gvisor`
|
|
371
|
+
(handler `runsc`) whose scheduling block selects nodes labelled
|
|
372
|
+
`wardby.dev/sandbox=gvisor` and tolerates their `sandbox=gvisor:NoSchedule`
|
|
373
|
+
taint.
|
|
374
|
+
|
|
375
|
+
Set `KUBERNETES_PLATFORM=gke-autopilot` on GKE Autopilot. It applies to native
|
|
376
|
+
worker pods as well as coding pods: pod resources are conformed to Autopilot's
|
|
377
|
+
rules (CPU in 250m steps, 1 to 6.5 GiB of memory per vCPU) so admission changes
|
|
378
|
+
nothing, and the attestation above then passes. Autopilot also requires a
|
|
379
|
+
sandboxing runtime: with this platform the server refuses to start unless
|
|
380
|
+
`NATIVE_SANDBOX_RUNTIME_CLASS` (or `KUBERNETES_RUNTIME_CLASS`) is `gvisor`
|
|
381
|
+
("runs native workers only under gVisor"). A cold gVisor node can take about
|
|
382
|
+
two minutes to come up, so raise `NATIVE_SANDBOX_READY_TIMEOUT_MS` (for example
|
|
383
|
+
to `600000`).
|
|
384
|
+
|
|
385
|
+
### Capacity
|
|
386
|
+
|
|
387
|
+
`NATIVE_SANDBOX_MAX_CONCURRENT` caps how many sandbox runs may be active at
|
|
388
|
+
once across all server replicas (it counts active gateway sessions). A start
|
|
389
|
+
past the cap fails immediately with
|
|
390
|
+
[`native_sandbox_capacity`](../help/errors/native-sandbox-capacity.md); it does
|
|
391
|
+
not queue. The same code is returned when the namespace's ResourceQuota has no
|
|
392
|
+
room for another worker pod. Raise the ResourceQuota together with
|
|
393
|
+
`NATIVE_SANDBOX_MAX_CONCURRENT` (and `CODING_MAX_CONCURRENT`, which shares the
|
|
394
|
+
namespace): each worker pod needs its CPU and memory limits from the quota.
|
|
395
|
+
|
|
396
|
+
## Warm pool
|
|
397
|
+
|
|
398
|
+
By default every sandbox run starts its own worker, so a run waits for the
|
|
399
|
+
worker to be created and, on Kubernetes, for pod scheduling, the image pull, and
|
|
400
|
+
the in-pod isolation proof. The warm pool keeps a few idle, already-isolated
|
|
401
|
+
workers ready so a run can take one instead. It works with both
|
|
402
|
+
`NATIVE_SANDBOX_LAUNCHER=docker` and `kubernetes`.
|
|
403
|
+
|
|
404
|
+
### Enable it
|
|
405
|
+
|
|
406
|
+
Set `NATIVE_SANDBOX_WARM_POOL_SIZE` on the server to the number of idle workers
|
|
407
|
+
to keep (0 to 50; the default `0` means no pool and every run cold-starts as
|
|
408
|
+
before). `NATIVE_SANDBOX_WARM_MAX_AGE_MS` (60000 to 21600000, default `1800000`,
|
|
409
|
+
30 minutes) is how long an idle worker may wait: an older one is replaced and
|
|
410
|
+
never claimed. A pool worker that nobody claims also exits by itself five
|
|
411
|
+
minutes after its max age.
|
|
412
|
+
|
|
413
|
+
The worker image must support pool workers: rebuild or update
|
|
414
|
+
`wardby-native-worker` with the release that adds the pool. An older image still
|
|
415
|
+
runs cold runs but cannot be a pool worker. The release adds a database
|
|
416
|
+
migration (the `NativeWarmWorker` table); apply it with `prisma migrate deploy`
|
|
417
|
+
as with any upgrade.
|
|
418
|
+
|
|
419
|
+
### Sizing and idle cost
|
|
420
|
+
|
|
421
|
+
`NATIVE_SANDBOX_MAX_CONCURRENT` still counts only active runs. Idle pool workers
|
|
422
|
+
are extra running containers or pods on top of it. On Kubernetes the namespace
|
|
423
|
+
ResourceQuota must have room for `NATIVE_SANDBOX_MAX_CONCURRENT` plus
|
|
424
|
+
`NATIVE_SANDBOX_WARM_POOL_SIZE` worker pods, plus the gateway and any coding
|
|
425
|
+
runs. A pool worker that does not fit is skipped and logged; it never fails a
|
|
426
|
+
run.
|
|
427
|
+
|
|
428
|
+
Each idle worker reserves its CPU and memory continuously. On GKE Autopilot you
|
|
429
|
+
pay for each idle pod's requests (500m and 512Mi in the reference overlay). In
|
|
430
|
+
return, a claimed run skips pod scheduling, the image pull, node boot, and the
|
|
431
|
+
in-pod isolation probe; on Autopilot a cold gVisor node can take about two
|
|
432
|
+
minutes to come up. Start with a small size and raise it if runs still
|
|
433
|
+
cold-start under load.
|
|
434
|
+
|
|
435
|
+
### How claims keep the isolation guarantees
|
|
436
|
+
|
|
437
|
+
- A pool worker is started and its isolation proven before any run exists (on
|
|
438
|
+
Kubernetes it is attested and probed from inside the pod, the same as a cold
|
|
439
|
+
pod). It holds no run data and no gateway capability.
|
|
440
|
+
- A run claims an idle worker atomically through the database, so claims are
|
|
441
|
+
safe across server replicas and processes. A worker is never given to two
|
|
442
|
+
runs.
|
|
443
|
+
- The run's input, including its one-time gateway capability, is delivered by
|
|
444
|
+
exec over stdin only after the claim. It never travels in the environment,
|
|
445
|
+
the command line, labels, or a Kubernetes Secret.
|
|
446
|
+
- At claim the worker is re-checked: still running, and the pod and
|
|
447
|
+
NetworkPolicy still as built (Docker: attached only to its own network). It is
|
|
448
|
+
not probed again; the max age bounds how old the isolation proof can be.
|
|
449
|
+
- The worker is single-use and destroyed after the run, exactly like a cold one.
|
|
450
|
+
|
|
451
|
+
### Fallback
|
|
452
|
+
|
|
453
|
+
- No idle worker, or the claimed one fails the re-check: the run cold-launches
|
|
454
|
+
as if there were no pool.
|
|
455
|
+
- Delivery fails: the worker is removed and, once it is confirmed gone, the run
|
|
456
|
+
cold-launches. If it cannot be confirmed gone within 30 seconds, the run fails
|
|
457
|
+
with
|
|
458
|
+
[`native_sandbox_warm_delivery_failed`](../help/errors/native-sandbox-warm-delivery-failed.md)
|
|
459
|
+
rather than risk a second worker holding the same capability.
|
|
460
|
+
|
|
461
|
+
### Upkeep
|
|
462
|
+
|
|
463
|
+
Every `wardby serve`, `wardby scheduler`, and `wardby mcp` process with the
|
|
464
|
+
sandbox configured keeps the pool up every 15 seconds. They coordinate through
|
|
465
|
+
the database, so running several never overfills it. Each pass starts workers up
|
|
466
|
+
to the size, retires idle workers that are older than the max age, gone, or over
|
|
467
|
+
the size (oldest first), and removes leftovers. Changing the worker image, CPU or
|
|
468
|
+
memory, runtime class, priority class, platform, namespace, gateway, or max age
|
|
469
|
+
retires all idle workers of the old configuration automatically, about a
|
|
470
|
+
minute after they started (a grace that keeps two replicas from retiring each
|
|
471
|
+
other's workers mid rolling update); they are never claimed meanwhile. Lowering the
|
|
472
|
+
size to `0` retires leftover pool workers within a pass, including any that a
|
|
473
|
+
replica still running the old size refills during a rolling update. A short-lived
|
|
474
|
+
command such as `wardby run` claims workers but does not warm new ones.
|
|
475
|
+
|
|
476
|
+
### Naming, labels, and logs
|
|
477
|
+
|
|
478
|
+
Pool workers are named `wardby-nwarm-<token>`. Kubernetes pods carry
|
|
479
|
+
`wardby.io/pool=warm` and `wardby.io/warm-worker=<token>`, and still carry
|
|
480
|
+
`wardby.io/component=native-run`, so the gateway NetworkPolicy admits them.
|
|
481
|
+
Docker containers carry `io.wardby.pool=warm`. No new RBAC is needed: the
|
|
482
|
+
permissions in [Server permissions](#3-server-permissions) already cover it.
|
|
483
|
+
|
|
484
|
+
List idle workers with
|
|
485
|
+
`kubectl get pods -n <namespace> -l wardby.io/pool=warm` or
|
|
486
|
+
`docker ps --filter label=io.wardby.pool=warm`.
|
|
487
|
+
|
|
488
|
+
Pool logs use the module `native-warm-pool`, with an `event` field:
|
|
489
|
+
|
|
490
|
+
| `event` | Meaning |
|
|
491
|
+
| ---------- | ------------------------------------------------------------------------------ |
|
|
492
|
+
| `warmed` | A pool worker became ready (with the time it took, in ms). |
|
|
493
|
+
| `claimed` | A run took an idle worker. |
|
|
494
|
+
| `miss` | No idle worker was available; the run cold-launched. |
|
|
495
|
+
| `fallback` | A claimed worker failed the re-check or delivery; see the run's outcome above. |
|
|
496
|
+
| `retired` | An idle worker was replaced (with the reason: `stale`, `gone`, or `excess`). |
|
|
497
|
+
| `reaped` | An abandoned or orphaned pool worker was removed. |
|
|
498
|
+
|
|
499
|
+
If runs keep logging `miss`, the pool is smaller than demand, workers cannot be
|
|
500
|
+
started (look for "starting a warm worker failed", and check the ResourceQuota),
|
|
501
|
+
or the server running upkeep is not up.
|
|
502
|
+
|
|
503
|
+
## Configuration reference
|
|
504
|
+
|
|
505
|
+
| Variable | Launcher | Required | Default | Purpose |
|
|
506
|
+
| --------------------------------------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
507
|
+
| `NATIVE_SANDBOX_LAUNCHER` | both | Yes | unset (sandbox off) | `docker` or `kubernetes`. Independent of `JOB_LAUNCHER`. |
|
|
508
|
+
| `NATIVE_SANDBOX_WORKER_IMAGE` | both | Yes | | Pinned worker image. Docker: `repo@sha256:...` or a local image id. Kubernetes: a registry digest `repo@sha256:...` only. |
|
|
509
|
+
| `NATIVE_GATEWAY_CONTAINER` | Docker | Yes | | Name of the running gateway container the server connects to each run's network. |
|
|
510
|
+
| `NATIVE_SANDBOX_NAMESPACE` | Kubernetes | No | `KUBERNETES_NAMESPACE`, else `wardby-coding` | Namespace for run pods and the gateway. |
|
|
511
|
+
| `KUBERNETES_CONTEXT` | Kubernetes | No | in-cluster or current context | Kubeconfig context the server uses. |
|
|
512
|
+
| `NATIVE_SANDBOX_RUNTIME_CLASS` | Kubernetes | No | `KUBERNETES_RUNTIME_CLASS`, else none | RuntimeClass for run pods (for example gVisor). |
|
|
513
|
+
| `NATIVE_GATEWAY_SERVICE` | Kubernetes | No | `wardby-native-gateway` | Name of the gateway Service in the run namespace. |
|
|
514
|
+
| `NATIVE_GATEWAY_URL` | both | No | Docker: `http://wardby-native-gateway:8790/native-gateway/v1/call`; Kubernetes: the Service's ClusterIP on 8790 | What workers dial. Change only if the gateway is reachable another way. Must be an `http(s)` URL. |
|
|
515
|
+
| `NATIVE_SANDBOX_CPUS` | both | No | `1` | CPU limit per worker (Kubernetes: request and limit). |
|
|
516
|
+
| `NATIVE_SANDBOX_MEMORY_MB` | both | No | `512` | Memory limit per worker (Docker: swap disabled; Kubernetes: request and limit). |
|
|
517
|
+
| `NATIVE_SANDBOX_PIDS` | Docker | No | `128` | Process limit per worker. Docker only: Kubernetes has no per-pod process limit. |
|
|
518
|
+
| `NATIVE_SANDBOX_MAX_CONCURRENT` | both | No | unset (no cap) | At most this many sandbox runs at once across all replicas (counts active gateway sessions). A start past it fails with `native_sandbox_capacity`. |
|
|
519
|
+
| `NATIVE_SANDBOX_WARM_POOL_SIZE` | both | No | `0` (no pool; range 0-50) | Idle, already-isolated workers kept ready for runs to claim. See [Warm pool](#warm-pool). On Kubernetes, size the ResourceQuota for this many extra pods. Must be `0` when `WARDBY_LOW_POWER=1`. |
|
|
520
|
+
| `NATIVE_SANDBOX_WARM_MAX_AGE_MS` | both | No | `1800000` (range 60000-21600000) | An idle pool worker older than this is replaced and never claimed. |
|
|
521
|
+
| `NATIVE_SANDBOX_READY_TIMEOUT_MS` | Kubernetes | No | `120000` (range 1000-1800000) | How long a worker pod may take to start running. Use about `600000` on GKE Autopilot, where a cold gVisor node takes about two minutes. |
|
|
522
|
+
| `NATIVE_SANDBOX_ENFORCEMENT_TIMEOUT_MS` | Kubernetes | No | `30000` (range 1000-600000) | How long the in-pod isolation proof may take. |
|
|
523
|
+
| `NATIVE_SANDBOX_PRIORITY_CLASS` | Kubernetes | No | `KUBERNETES_RUN_PRIORITY_CLASS`, else none | PriorityClass for worker pods. Must not be a `system-` class. |
|
|
524
|
+
| `KUBERNETES_PLATFORM` | Kubernetes | No | none | `gke-autopilot` conforms worker pod resources to Autopilot's rules and requires the gVisor runtime class; `eks` targets EKS with a gVisor node group and also requires it. Shared with coding runs. |
|
|
525
|
+
|
|
526
|
+
Gateway-side variables (`NATIVE_GATEWAY_LISTEN`, `NATIVE_GATEWAY_DENY_PORT`) are
|
|
527
|
+
set on the gateway, not the server.
|
|
528
|
+
|
|
529
|
+
## Limits and deadline
|
|
530
|
+
|
|
531
|
+
- Each worker is limited by `NATIVE_SANDBOX_CPUS`, `NATIVE_SANDBOX_MEMORY_MB`,
|
|
532
|
+
and (Docker only) `NATIVE_SANDBOX_PIDS`, with a 64 MiB `/tmp`. Raise the
|
|
533
|
+
limits if tools run out of memory or processes.
|
|
534
|
+
- A sandbox run has a maximum lifetime of 60 minutes. Past it the worker is
|
|
535
|
+
stopped and the run fails with `native_sandbox_deadline_exceeded`.
|
|
536
|
+
- The agent's own turn and budget limits still apply as for any native agent.
|
|
537
|
+
|
|
538
|
+
## Sub-agents
|
|
539
|
+
|
|
540
|
+
A sandboxed run can delegate to sub-agents, including several in parallel.
|
|
541
|
+
Sub-agents of a sandboxed run are always **managed runs**: the gateway records
|
|
542
|
+
the delegation, and the server's scheduler leader starts the child on its next
|
|
543
|
+
tick (about every 10 seconds). Each delegation therefore adds up to about ten
|
|
544
|
+
seconds of start-up latency, and `wardby serve` or `wardby scheduler` must be
|
|
545
|
+
running or the child never starts.
|
|
546
|
+
|
|
547
|
+
A child runs in its own configured mode (a `control-plane` child runs in the
|
|
548
|
+
server, a `sandbox` child in its own worker). Cancelling a parent run stops its
|
|
549
|
+
open children.
|
|
550
|
+
|
|
551
|
+
## Budget behavior
|
|
552
|
+
|
|
553
|
+
The gateway is the budget authority for sandboxed runs. Before each model call
|
|
554
|
+
it reserves that call's worst-case cost against the run's budget (and any
|
|
555
|
+
budget group), calls the provider, then settles at the actual usage. A call the
|
|
556
|
+
remaining budget cannot cover is refused and the run ends `budget_exhausted`
|
|
557
|
+
instead of overspending. The worker cannot raise or bypass its budget because it
|
|
558
|
+
never talks to the provider directly.
|
|
559
|
+
|
|
560
|
+
## Failure modes
|
|
561
|
+
|
|
562
|
+
| Error code | Meaning | What to do |
|
|
563
|
+
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
564
|
+
| [`native_sandbox_unavailable`](../help/errors/native-sandbox-unavailable.md) | Agent is in `sandbox` mode but the server has no native sandbox. Fails before any spend. | Configure the sandbox (steps above) or set the agent back to `control-plane`. |
|
|
565
|
+
| [`native_sandbox_worker_exited`](../help/errors/native-sandbox-worker-exited.md) | The worker ended without producing a result. | Check the tool code and the worker's memory and PID limits; run again. |
|
|
566
|
+
| [`native_sandbox_deadline_exceeded`](../help/errors/native-sandbox-deadline-exceeded.md) | The run passed its 60-minute maximum; the worker was stopped. | Split the work into smaller runs or sub-agents. |
|
|
567
|
+
| [`native_sandbox_worker_lost`](../help/errors/native-sandbox-worker-lost.md) | The worker container or pod disappeared (for example its host or node restarted). It is never relaunched. | Trigger the run again; check the Docker host or node stability. |
|
|
568
|
+
| [`native_sandbox_image_not_pinned`](../help/errors/native-sandbox-image-not-pinned.md) | `NATIVE_SANDBOX_WORKER_IMAGE` is a mutable tag. | Use a `repo@sha256:...` digest or a local image id. |
|
|
569
|
+
| [`native_sandbox_docker_failed`](../help/errors/native-sandbox-docker-failed.md) | A Docker command (pull, network create, connect, run) failed. | Read the message, then check the Docker daemon, image availability, and gateway name. |
|
|
570
|
+
| [`native_sandbox_network_unenforced`](../help/errors/native-sandbox-network-unenforced.md) | Kubernetes: the pod's egress isolation could not be proven in time; the pod was removed. | Check that the CNI enforces NetworkPolicy and the gateway Service exposes 8790 and 8791. |
|
|
571
|
+
| [`native_sandbox_isolation_mismatch`](../help/errors/native-sandbox-isolation-mismatch.md) | Kubernetes: the stored pod or NetworkPolicy differs from what Wardby built (an admission change). | Exempt the run namespace's native-run pods from mutating policies. |
|
|
572
|
+
| [`native_sandbox_gateway_unavailable`](../help/errors/native-sandbox-gateway-unavailable.md) | Kubernetes: the gateway Service is missing or has no ClusterIP. | Create the Service (ClusterIP, not headless) or fix `NATIVE_GATEWAY_SERVICE`. |
|
|
573
|
+
| [`native_sandbox_worker_unready`](../help/errors/native-sandbox-worker-unready.md) | Kubernetes: the worker pod did not reach Running in time. | Check image pull, quotas, and scheduling with `kubectl describe pod`. |
|
|
574
|
+
| [`native_sandbox_capacity`](../help/errors/native-sandbox-capacity.md) | `NATIVE_SANDBOX_MAX_CONCURRENT` runs are already active, or the namespace ResourceQuota is full. Not queued. | Wait and retry, or raise the cap together with the ResourceQuota. |
|
|
575
|
+
| [`native_sandbox_warm_delivery_failed`](../help/errors/native-sandbox-warm-delivery-failed.md) | A claimed warm worker could not receive the run's input and could not be confirmed stopped within 30 seconds. | Trigger the run again; check the node or Docker host and pod exec access. |
|
|
576
|
+
| [`native_sandbox_requires_catalog`](../help/errors/native-sandbox-requires-catalog.md) | No model catalog entry was recorded for the run: the process running it does not use the catalog-backed model router. | Run it on a standard `wardby serve`/`scheduler`/`mcp` process, or keep the agent on `control-plane`. |
|
|
577
|
+
|
|
578
|
+
A run's error appears in its `error` field in `get_run` and `list_runs`.
|
|
579
|
+
|
|
580
|
+
## Roll back
|
|
581
|
+
|
|
582
|
+
Set the agent back to `control-plane` with `update_agent` or
|
|
583
|
+
`wardby agent mode <name> control-plane`. Only new runs are
|
|
584
|
+
affected: runs already started in sandbox mode finish there. To switch sandbox mode off for the whole
|
|
585
|
+
deployment, unset `NATIVE_SANDBOX_LAUNCHER`; sandbox-mode agents then fail
|
|
586
|
+
closed with `native_sandbox_unavailable` rather than running unisolated, so move
|
|
587
|
+
them back to `control-plane` first if they should keep running.
|
|
588
|
+
|
|
589
|
+
## Acceptance tests
|
|
590
|
+
|
|
591
|
+
Three opt-in checks exercise the real isolation against a real runtime. Each is
|
|
592
|
+
skipped unless its script sets its flag:
|
|
593
|
+
|
|
594
|
+
- `npm run test:native-docker`: the Docker launcher on the local Docker host.
|
|
595
|
+
- `npm run test:native-kind`: the Kubernetes launcher on a kind cluster
|
|
596
|
+
(`NATIVE_TEST_KIND_WORKER_IMAGE`).
|
|
597
|
+
- `npm run test:native-cluster`: launcher-only isolation checks against the
|
|
598
|
+
deployed gateway of any cluster. It needs no database or model. Set
|
|
599
|
+
`NATIVE_TEST_WORKER_IMAGE` (required, a registry digest) and, as needed,
|
|
600
|
+
`NATIVE_TEST_CONTEXT`, `NATIVE_TEST_NAMESPACE`, `NATIVE_TEST_PLATFORM`,
|
|
601
|
+
`NATIVE_TEST_RUNTIME_CLASS`, `NATIVE_TEST_PRIORITY_CLASS`, and
|
|
602
|
+
`NATIVE_TEST_FORBIDDEN`, a comma-separated list of `host:port` addresses that
|
|
603
|
+
must be unreachable from a worker (for example the database's private IP on
|
|
604
|
+
port 3307).
|
|
605
|
+
|
|
606
|
+
## Troubleshooting
|
|
607
|
+
|
|
608
|
+
- **Gateway health.** `GET /healthz` on the gateway returns `{"ok":true}` when
|
|
609
|
+
its database answers and 503 otherwise. From the Docker host:
|
|
610
|
+
`docker exec wardby-native-gateway node -e "fetch('http://127.0.0.1:8790/healthz').then(r => r.text()).then(console.log)"`.
|
|
611
|
+
- **List worker containers.** Workers and networks carry the label
|
|
612
|
+
`io.wardby.component=native-worker`:
|
|
613
|
+
`docker ps --all --filter label=io.wardby.component=native-worker`.
|
|
614
|
+
The server removes finished workers and, at startup, sweeps leftovers whose
|
|
615
|
+
runs have ended or passed their deadline.
|
|
616
|
+
- **Kubernetes: list worker pods.** Run pods carry the label
|
|
617
|
+
`wardby.io/component=native-run`:
|
|
618
|
+
`kubectl get pods -n <namespace> -l wardby.io/component=native-run`.
|
|
619
|
+
The server removes finished pods (with their Secret and NetworkPolicy) and,
|
|
620
|
+
at startup, sweeps leftovers whose runs have ended or passed their deadline.
|
|
621
|
+
- **Kubernetes: gateway health.** `kubectl get deploy,svc,endpoints -n <namespace> wardby-native-gateway`;
|
|
622
|
+
the Service must list ports 8790 and 8791 and have ready endpoints.
|
|
623
|
+
- **Check a run.** Call `get_run` for the run's status and error code, then use
|
|
624
|
+
the matching error article from `search_help` or `get_help_article`.
|
|
625
|
+
- **Workers cannot reach the gateway.** Confirm `NATIVE_GATEWAY_CONTAINER`
|
|
626
|
+
matches the running container's name exactly and that the gateway listens on
|
|
627
|
+
the port in `NATIVE_GATEWAY_URL` (default 8790).
|
|
628
|
+
- **Sub-agents never start.** Confirm `wardby serve` or `wardby scheduler` is
|
|
629
|
+
running; the scheduler leader starts them.
|
|
630
|
+
- **Tool code fails inside the sandbox.** The worker has no network except the
|
|
631
|
+
gateway and a read-only filesystem apart from `/tmp`; tools must use the
|
|
632
|
+
provided fetch, datastore, and secret functions.
|
|
633
|
+
|
|
634
|
+
See also [Security deployment](security-deployment.md) and the
|
|
635
|
+
[runtime architecture](architecture-runtime.md).
|
|
@@ -40,6 +40,15 @@ repository-scoped GitHub App installation, and a deliberately small budget.
|
|
|
40
40
|
Verify the requested diff, terminal outcome, cost, resource cleanup, and branch
|
|
41
41
|
cleanup. See [local coding-agent setup](coding-agent-setup.md).
|
|
42
42
|
|
|
43
|
+
The bundled help catalog is built for the version in `package.json`.
|
|
44
|
+
`appliesTo` on each help article must name its minimum release; later articles
|
|
45
|
+
stay in source but are omitted from that release's CLI and MCP help. The build
|
|
46
|
+
fails when an included article links to an omitted one. When adding a newer
|
|
47
|
+
feature to an older article, move its guidance into a suitably versioned
|
|
48
|
+
article or update the older article so it remains accurate for its stated
|
|
49
|
+
minimum release. Mark newer features in the public `docs/` guides with their
|
|
50
|
+
minimum release as well.
|
|
51
|
+
|
|
43
52
|
Before production deployment, review [runtime architecture](architecture-runtime.md),
|
|
44
53
|
[coding-worker isolation](coding-worker-isolation.md), and the
|
|
45
54
|
[security deployment guide](security-deployment.md). A self-hosted operator is
|