diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f83d72c6..b258be08 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -70,8 +70,11 @@ jobs:
if: runner.os == 'Linux'
- run: cargo check --locked -p devo-kernel --all-targets --target ${{ matrix.target }}
if: runner.os == 'macOS'
- - run: cargo test --locked -p devo-kernel credential_unix::tests
+ - name: Test kernel credential environment and host protocol
if: runner.os == 'macOS'
+ run: |
+ cargo test --locked -p devo-kernel session::credential_env_tests
+ cargo test --locked -p devo-kernel protocol::tests
windows-kernel:
needs: changes
@@ -151,24 +154,6 @@ jobs:
- run: npm test
- run: node --import tsx --test lib/coding-agent/src/modes/interactive/interactive-mode-model-candidates.test.ts lib/coding-agent/src/modes/interactive/interactive-mode-session-ancillary.test.ts
- tmux-secrets:
- name: Detect live TUI provider secrets
- runs-on: ubuntu-latest
- outputs:
- live: ${{ steps.detect.outputs.live }}
- steps:
- # Job-level `if` cannot read the secrets context; fork PRs see empty
- # secrets, so this doubles as the fork gate for the live job.
- - id: detect
- env:
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
- run: |
- if [ -n "$DEEPSEEK_API_KEY" ]; then
- echo "live=true" >> "$GITHUB_OUTPUT"
- else
- echo "live=false" >> "$GITHUB_OUTPUT"
- fi
-
tmux:
needs: changes
if: needs.changes.outputs.rust == 'true' || needs.changes.outputs.tui == 'true'
@@ -209,89 +194,12 @@ jobs:
mock_pid=$!
trap 'kill "$mock_pid" 2>/dev/null || true' EXIT
printf '%s\n' \
- '{"model": "deepseek/deepseek-v4-flash", "provider": {"deepseek": {"credential": "deepseek", "enabled": true, "base_url": "http://127.0.0.1:8801"}}}' \
+ '{"model": "deepseek/deepseek-v4-flash", "provider": {"deepseek": {"credential": "deepseek", "enabled": true, "base_url": "http://127.0.0.1:8801", "wire_api": "openai_chat_completions"}}}' \
> "$home/providers.json"
printf '%s\n' '{"deepseek": {"type": "api_key", "key": "ci-mock-key"}}' > "$home/auth.json"
chmod 600 "$home/auth.json"
DEVO_HOME="$home" node scripts/run-tmux-smoke.js T2
- tmux-live:
- needs: [changes, tmux-secrets]
- if: >-
- needs.tmux-secrets.outputs.live == 'true' &&
- (needs.changes.outputs.rust == 'true' || needs.changes.outputs.tui == 'true')
- name: TUI tmux live LLM (${{ matrix.os }})
- runs-on: ${{ matrix.os }}
- timeout-minutes: 40
- strategy:
- fail-fast: false
- matrix:
- # macOS covers the seatbelt fence and python3 fallback paths that
- # Linux cannot reach with the same code.
- os: [ubuntu-latest, macos-latest]
- env:
- DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
- OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- steps:
- - uses: actions/checkout@v4
- - uses: dtolnay/rust-toolchain@stable
- - uses: Swatinem/rust-cache@v2
- - uses: actions/setup-node@v4
- with:
- node-version: "24"
- cache: npm
- cache-dependency-path: apps/tui/package-lock.json
- - name: Ensure tmux
- run: command -v tmux || (if [ "$(uname)" = Darwin ]; then brew install tmux; else sudo apt-get update && sudo apt-get install -y tmux; fi)
- - name: Install TUI dependencies
- working-directory: apps/tui
- run: npm ci --ignore-scripts --no-audit --no-fund
- - name: Build CLI
- run: cargo build -p devo-cli --bin devo --locked
- - name: Seed live Devo homes (one per wire API)
- run: |
- # chat-completions: builtin DeepSeek endpoint
- chat="$(mktemp -d)/home"; mkdir -p "$chat"
- printf '%s\n' '{"model": "deepseek/deepseek-v4-flash", "provider": {"deepseek": {"credential": "deepseek", "enabled": true, "base_url": "https://api.deepseek.com", "wire_api": "openai_chat_completions"}}}' > "$chat/providers.json"
- # anthropic-messages: DeepSeek's Anthropic-compatible endpoint, same key
- anth="$(mktemp -d)/home"; mkdir -p "$anth"
- printf '%s\n' '{"model": "deepseek/deepseek-v4-flash", "provider": {"deepseek": {"credential": "deepseek", "enabled": true, "base_url": "https://api.deepseek.com/anthropic", "wire_api": "anthropic_messages"}}}' > "$anth/providers.json"
- # responses: builtin OpenAI (openai_responses wire)
- resp="$(mktemp -d)/home"; mkdir -p "$resp"
- printf '%s\n' '{"model": "openai/gpt-5-mini", "provider": {"openai": {"credential": "openai", "enabled": true, "base_url": "https://api.openai.com/v1", "wire_api": "openai_responses"}}}' > "$resp/providers.json"
- for home in "$chat" "$anth"; do
- node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ deepseek: { type: "api_key", key: process.env.DEEPSEEK_API_KEY } }, null, 2));' "$home/auth.json"
- chmod 600 "$home/auth.json"
- done
- node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ openai: { type: "api_key", key: process.env.OPENAI_API_KEY } }, null, 2));' "$resp/auth.json"
- chmod 600 "$resp/auth.json"
- echo "DEVO_LIVE_CHAT_HOME=$chat" >> "$GITHUB_ENV"
- echo "DEVO_LIVE_ANTHROPIC_HOME=$anth" >> "$GITHUB_ENV"
- echo "DEVO_LIVE_RESPONSES_HOME=$resp" >> "$GITHUB_ENV"
- - name: Chat Completions wire (real DeepSeek)
- working-directory: apps/tui
- env:
- DEVO_TUI_TEST_TIMEOUT_MS: "180000"
- run: DEVO_HOME="$DEVO_LIVE_CHAT_HOME" node scripts/run-tmux-smoke.js T2
- - name: Anthropic Messages wire (real DeepSeek)
- working-directory: apps/tui
- env:
- DEVO_TUI_TEST_TIMEOUT_MS: "180000"
- run: DEVO_HOME="$DEVO_LIVE_ANTHROPIC_HOME" node scripts/run-tmux-smoke.js T2
- - name: Responses wire (real OpenAI)
- # Optional second secret; skipped when absent. The secrets context is
- # not available in step-level `if`, so read the job env mirror.
- if: env.OPENAI_API_KEY != ''
- working-directory: apps/tui
- env:
- DEVO_TUI_TEST_TIMEOUT_MS: "180000"
- run: DEVO_HOME="$DEVO_LIVE_RESPONSES_HOME" node scripts/run-tmux-smoke.js T2
- - name: Interrupted-turn scenario (real DeepSeek, Chat Completions)
- working-directory: apps/tui
- env:
- DEVO_TUI_TEST_TIMEOUT_MS: "180000"
- run: DEVO_HOME="$DEVO_LIVE_CHAT_HOME" node scripts/run-tmux-smoke.js T3
-
check:
needs: changes
if: needs.changes.outputs.rust == 'true'
@@ -312,6 +220,11 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.13"
+ - name: Install kernel snapshot dependency
+ run: python -m pip install dill==0.4.1
- run: cargo test --workspace --jobs 1
fmt:
diff --git a/.gitignore b/.gitignore
index 918474c2..ddf29c76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
.DS_Store
.claude
__pycache__/
+.venv/
node_modules
/apps/desktop/out/
/apps/desktop/resources/runtime/
diff --git a/Cargo.lock b/Cargo.lock
index 2d0b429a..7a4c0167 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1403,6 +1403,7 @@ dependencies = [
"colored",
"devo-arg0",
"devo-core",
+ "devo-harness",
"devo-kernel",
"devo-mcp",
"devo-protocol",
@@ -1416,6 +1417,7 @@ dependencies = [
"pretty_assertions",
"serde",
"serde_json",
+ "tempfile",
"tokio",
"toml",
"tracing",
@@ -1544,6 +1546,7 @@ name = "devo-harness"
version = "0.2.16"
dependencies = [
"chrono",
+ "fs2",
"pretty_assertions",
"serde",
"serde_json",
@@ -1557,10 +1560,7 @@ name = "devo-kernel"
version = "0.2.16"
dependencies = [
"anyhow",
- "devo-sandbox",
"devo-util-paths",
- "devo-util-process",
- "devo-windows-sandbox",
"dunce",
"futures",
"libc",
@@ -1570,6 +1570,7 @@ dependencies = [
"tempfile",
"thiserror 2.0.18",
"tokio",
+ "tokio-util",
"tracing",
"uuid",
]
diff --git a/README.ja.md b/README.ja.md
index 8fdd0caf..dbbce0d1 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -66,11 +66,11 @@ DeepSeek、Qwen、Kimi、GLM、MiniMax が含まれます。
`/model` でモデルを切り替え、`devo onboard` でプロバイダーを再設定できます。
手動設定、認証情報、MCP、オフラインインストール、リリースミラーは
-[ユーザーガイド(英語)](./docs/usage.md)を参照してください。
+[ユーザーガイド(英語)](./docs/user-guide.md)を参照してください。
## ドキュメントと貢献
-- [ユーザーガイド(英語)](./docs/usage.md) — インストール、プロバイダー、設定、オフライン利用。
+- [ユーザーガイド(英語)](./docs/user-guide.md) — インストール、プロバイダー、設定、オフライン利用。
- [TUI ガイド(英語)](./apps/tui/README.md) — ターミナル操作とコマンド。
- [貢献ガイド(英語)](./CONTRIBUTING.md) — 問題報告、ドキュメント改善、小規模な修正を歓迎します。
diff --git a/README.md b/README.md
index 9cd0a600..64c51e8a 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,22 @@ The agent can filter large outputs in Python, reuse earlier work, and delegate
independent subtasks to child agents with their own sessions and Python state.
MCP, skills, permissions, and resumable sessions round out the workflow.
+Children can independently select a configured model and reasoning level:
+
+```python
+models = await rlm.find_models(limit=100)
+chosen = next((m for m in models if "high" in m.thinking_levels), None)
+if chosen is not None:
+ reviewer = await rlm.spawn(
+ "Review the current diff.", name="reviewer",
+ model=chosen.selector, thinking="high",
+ )
+```
+
+Omitted options inherit the immediate parent. Invalid selections fail without
+fallback. Discovery reflects configuration, not live health or credits. See the
+[child model API](docs/rlm-child-models.md).
+
## Quick start
**Desktop:** Download the package for your OS and architecture from
@@ -59,12 +75,12 @@ Terminal bundles include Node.js and Python. On Windows, install
[Git for Windows](https://gitforwindows.org/) for shell commands and approve
the one-time sandbox setup prompt.
-> **Temporarily disabled.** OS sandboxing and the permission picker are
-> disabled repo-wide: every process runs with full user permissions and new
-> sessions default to full-access. Revert by flipping
-> `SANDBOX_ENFORCEMENT_TEMPORARILY_DISABLED` (Rust, `crates/sandbox/src/lib.rs`),
-> `PERMISSIONS_COMMAND_ENABLED` (TUI), `PERMISSION_PICKER_ENABLED` (desktop),
-> and the default-preset literals back to their asking defaults.
+> Shell and terminal execution follow the configured sandbox profile; there is
+> no global sandbox-disable switch. Python kernels are trusted user-privileged
+> processes, not OS-isolated environments. See
+> [the execution boundary](docs/trusted-kernel-host-shell.md).
+> Permission-picker UI flags and new-session permission defaults are separate
+> settings; removing the global switch does not change them.
## Configuration
@@ -74,11 +90,11 @@ DeepSeek, Qwen, Kimi, GLM, and MiniMax. Use `/model` to switch models or
`devo onboard` to revisit provider setup.
For manual configuration, credentials, MCP, offline installation, and release
-mirrors, see the [user guide](./docs/usage.md).
+mirrors, see the [user guide](./docs/user-guide.md).
## Docs and contributing
-- [User guide](./docs/usage.md) — installation, providers, configuration, and offline use.
+- [User guide](./docs/user-guide.md) — installation, providers, configuration, and offline use.
- [TUI guide](./apps/tui/README.md) — terminal controls and commands.
- [Contributing](./CONTRIBUTING.md) — issues, documentation, and focused fixes welcome.
diff --git a/README.ru.md b/README.ru.md
index 580eeb6c..9e41e81f 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -68,11 +68,11 @@ devo
Переключайте модели через `/model`, повторно настраивайте провайдера через `devo onboard`.
Ручная конфигурация, учётные данные, MCP, офлайн-установка и зеркала релизов
-описаны в [руководстве пользователя (на английском)](./docs/usage.md).
+описаны в [руководстве пользователя (на английском)](./docs/user-guide.md).
## Документация и участие
-- [Руководство пользователя (на английском)](./docs/usage.md) — установка, провайдеры, конфигурация и офлайн-работа.
+- [Руководство пользователя (на английском)](./docs/user-guide.md) — установка, провайдеры, конфигурация и офлайн-работа.
- [Руководство TUI (на английском)](./apps/tui/README.md) — управление терминалом и команды.
- [Участие в разработке (на английском)](./CONTRIBUTING.md) — приветствуются сообщения об ошибках, улучшения документации и небольшие исправления.
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 60bdda60..e7b640ca 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -61,11 +61,11 @@ devo
**Anthropic Messages** API。内置目录包含 DeepSeek、Qwen、Kimi、GLM 和 MiniMax。
用 `/model` 切换模型,用 `devo onboard` 重新配置提供商。
-手动配置、凭据、MCP、离线安装和发布镜像见[使用指南(英文)](./docs/usage.md)。
+手动配置、凭据、MCP、离线安装和发布镜像见[使用指南(英文)](./docs/user-guide.md)。
## 文档与贡献
-- [使用指南(英文)](./docs/usage.md) — 安装、提供商、配置与离线使用。
+- [使用指南(英文)](./docs/user-guide.md) — 安装、提供商、配置与离线使用。
- [TUI 指南(英文)](./apps/tui/README.md) — 终端操作与命令。
- [贡献指南(英文)](./CONTRIBUTING.md) — 欢迎问题反馈、文档改进和小范围修复。
diff --git a/README.zh-Hant.md b/README.zh-Hant.md
index 5cb1a06c..67183d27 100644
--- a/README.zh-Hant.md
+++ b/README.zh-Hant.md
@@ -61,11 +61,11 @@ devo
**Anthropic Messages** API。內建目錄包含 DeepSeek、Qwen、Kimi、GLM 和 MiniMax。
用 `/model` 切換模型,用 `devo onboard` 重新設定供應商。
-手動設定、憑證、MCP、離線安裝和發行鏡像請見[使用指南(英文)](./docs/usage.md)。
+手動設定、憑證、MCP、離線安裝和發行鏡像請見[使用指南(英文)](./docs/user-guide.md)。
## 文件與貢獻
-- [使用指南(英文)](./docs/usage.md) — 安裝、供應商、設定與離線使用。
+- [使用指南(英文)](./docs/user-guide.md) — 安裝、供應商、設定與離線使用。
- [TUI 指南(英文)](./apps/tui/README.md) — 終端操作與命令。
- [貢獻指南(英文)](./CONTRIBUTING.md) — 歡迎問題回報、文件改進和小範圍修正。
diff --git a/apps/desktop/docs/windows-desktop-qa.md b/apps/desktop/docs/windows-desktop-qa.md
index 251896ce..763390fd 100644
--- a/apps/desktop/docs/windows-desktop-qa.md
+++ b/apps/desktop/docs/windows-desktop-qa.md
@@ -64,7 +64,7 @@ replaced by scripted browser end-to-end tests.
| Provider failure | HTTP 402 was visible as “Insufficient provider balance,” with expandable details. |
| Funded recovery | A new real Electron request produced assistant output, Python output, and a fixture read after balance was replenished. No new 402 occurred. |
| Funded automatic queue | Enter queued a follow-up during a 60-second Python cell; it drained automatically and produced its own Python result and assistant reply. |
-| Mouse submission during work | Clicked Queue message during a fenced 150-second Python cell. Edited and resubmitted the first queued entry, then queued and steered a second. The steer produced 402; the edited queue drained automatically and produced 301. Stop remains separately available. |
+| Mouse submission during work | Clicked Queue message during a fenced 150-second Python cell (historical: the QA build then sandboxed the Python kernel). Edited and resubmitted the first queued entry, then queued and steered a second. The steer produced 402; the edited queue drained automatically and produced 301. Stop remains separately available. |
| Completed activity label | Removed the tool suffix: completed activity displays “Worked for 1s,” with Python details inside the disclosure. |
| Separate inner cells | Clicked Worked to reveal collapsed Thought and Python rows. Opened each independently and collapsed the whole group with Worked. Python source and output stay paired. |
| Steering visibility after completion | A fresh steer during a 90-second Python wait remained a user bubble after completion, runtime restart, and renderer reload. The assistant applied it and produced 503. The reported disappearance was not reproduced in this checkout; live/replay regression tests preserve it. |
@@ -84,6 +84,16 @@ a fresh real Electron Python turn reported the token `DevoSandboxOffline`,
used the fenced kernel, and emitted no new unfenced warning. Historical
warnings remain attached to their original turns.
+Historical note: the Python-turn sentences above (the `DevoSandboxOffline`
+token, the fenced kernel, and the no-new-unfenced-warning result) describe the
+pre-cleanup build, where the Python kernel itself ran in the Windows sandbox.
+Current builds run the Python kernel as a trusted, user-privileged process with
+no OS sandbox (`docs/trusted-kernel-host-shell.md`); only host shell execution
+follows the configured sandbox profile. The earlier write/network probe is
+historical evidence, not validation of the current host-shell path; that path
+requires a fresh Windows runtime check. A current Python turn should not be
+expected to report that token or a fenced-kernel state.
+
Funded Electron requests now succeed. The earlier 402 checks remain coverage
of provider failure handling, rather than a current provider-balance blocker.
diff --git a/apps/desktop/packages/devo-ai-sdk/src/v2/client-native-interactions.test.ts b/apps/desktop/packages/devo-ai-sdk/src/v2/client-native-interactions.test.ts
index d48d9ff9..f2b65b8d 100644
--- a/apps/desktop/packages/devo-ai-sdk/src/v2/client-native-interactions.test.ts
+++ b/apps/desktop/packages/devo-ai-sdk/src/v2/client-native-interactions.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test"
+import { buildAutomationPromptParts } from "../../../../src/main/automation/prompt"
import {
DESKTOP_INITIALIZE_PARAMS,
createDevoClient,
@@ -1138,6 +1139,26 @@ describe("Native desktop SDK interactions", () => {
})
})
+ test("delivers automation run guidance and task through the Native queue", async () => {
+ const transport = new FakeNativeTransport()
+ const client = createDevoClient({ directory: "/repo", transport })
+ await client.session.create()
+ const parts = buildAutomationPromptParts("Review today's changes", "Daily review", "memory.md")
+
+ await client.session.promptAsync({ sessionId: "session-1", parts })
+
+ const requests = transport.requests.filter((request) => request.method === "session/queue/push")
+ expect(requests).toEqual([{
+ method: "session/queue/push",
+ params: {
+ sessionId: "session-1",
+ input: [{ type: "text", text: `${parts[0].text}\nReview today's changes` }],
+ idempotencyKey: expect.any(String),
+ },
+ directory: "/repo",
+ }])
+ })
+
test("keeps the session busy after turn/start until turn/completed", async () => {
const transport = new FakeNativeTransport()
const client = createDevoClient({ directory: "/repo", transport })
diff --git a/apps/desktop/packages/devo-ai-sdk/src/v2/client.ts b/apps/desktop/packages/devo-ai-sdk/src/v2/client.ts
index bbf9d81b..a64b77d7 100644
--- a/apps/desktop/packages/devo-ai-sdk/src/v2/client.ts
+++ b/apps/desktop/packages/devo-ai-sdk/src/v2/client.ts
@@ -1486,9 +1486,7 @@ class NativeClient {
},
discover: async (params: ProviderDiscoverParams): Promise