Handoff-style /model switch command and per-turn thinking level (#64)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-26 16:31:51 +08:00
committed by GitHub
parent 816a823b75
commit 1e23452f48
58 changed files with 1064 additions and 336 deletions
+1
View File
@@ -59,6 +59,7 @@ for await (const output of session.run([userText("Clean up the CSV files under d
interface RunOptions {
signal?: AbortSignal; // interrupt (e.g. Ctrl-C)
approve?: ApproveFn; // per-tool approval; denies everything when omitted (conservative default)
thinkingLevel?: ThinkingLevelName; // this run's thinking level (per-turn, carried through reconnect retries; compaction requests keep the default)
}
```
+1
View File
@@ -56,6 +56,7 @@ for await (const output of session.run([userText("整理 data/ 下的 CSV 文件
interface RunOptions {
signal?: AbortSignal; // 中断信号(如 Ctrl-C)
approve?: ApproveFn; // 逐工具审批;未注入时默认全部拒绝(保守策略)
thinkingLevel?: ThinkingLevelName; // 本次 run 的思考等级(逐轮参数,覆盖到重连重试;压缩请求用默认值)
}
```
+1 -1
View File
@@ -95,7 +95,7 @@ Edit this file via the CLI (`penguin config model …`) or the Web Models page
| `system_prompt` | built-in template | Required; the only template with placeholder substitution |
| `max_turns` | `100` | Maximum LLM turns per Task (-1 removes the cap) |
| `model.max_tokens` | `32000` | Output Token limit per Request (-1 = no cap, provider default) |
| `model.thinking_level` | `medium` | `none` / `low` / `medium` / `high` / `xhigh` |
| `model.thinking_level` | `medium` | `none` / `low` / `medium` / `high` / `xhigh`; the session default, overridable per-Task |
| `model.timeoutMs` | `120000` | Per-Request timeout (milliseconds) |
| `compaction.max_context_length` | `128000` | Context Token threshold that triggers compaction |
| `compaction.max_session_turns` | `-1` | Cumulative Session turn threshold (`-1` = unlimited) |
+1 -1
View File
@@ -95,7 +95,7 @@ output = 0.857143
| `system_prompt` | 内置模板 | 必填;唯一进行占位符替换的模板 |
| `max_turns` | `100` | 单个 Task 的最大 LLM 轮数(-1 不限制) |
| `model.max_tokens` | `32000` | 单次输出 Token 上限(-1 不设上限,用服务商默认) |
| `model.thinking_level` | `medium` | `none` / `low` / `medium` / `high` / `xhigh` |
| `model.thinking_level` | `medium` | `none` / `low` / `medium` / `high` / `xhigh`;作为会话默认档位,可被逐轮 Task 参数覆盖 |
| `model.timeoutMs` | `120000` | 单次 Request 超时(毫秒) |
| `compaction.max_context_length` | `128000` | 触发压缩的上下文 Token 阈值 |
| `compaction.max_session_turns` | `-1` | Session 累计轮数阈值(`-1` 不限制) |
+3 -1
View File
@@ -40,6 +40,7 @@ interface LLMInterface {
interface GenerativeModelParameters {
newMessages: OmniMessage[]; // only this turn's new messages (the impl owns history; mixed roles rejected)
signal?: AbortSignal;
thinkingLevel?: ThinkingLevelName; // per-request override; omitted = the construction default
}
```
@@ -78,7 +79,7 @@ interface GenerativeModelConfig {
systemPrompt?: string; // fully assembled system prompt, placeholders substituted
contextWindow?: number;
maxTokens?: number;
thinkingLevel?: ThinkingLevelName; // "none" | "low" | "medium" | "high" | "xhigh"
thinkingLevel?: ThinkingLevelName; // construction default (a per-request parameter can override); "none" | "low" | "medium" | "high" | "xhigh"
requestTimeoutMs?: number; // per-Request timeout, default 120000; <=0 disables
toolCallIds?: ToolCallIdAllocator; // Session-level tool_call_id registry (pass the same instance across compaction)
}
@@ -179,6 +180,7 @@ session.run(
interface RunOptions {
signal?: AbortSignal; // interrupt (e.g. Ctrl-C)
approve?: ApproveFn; // per-tool approval; denies everything when omitted
thinkingLevel?: ThinkingLevelName; // this run's thinking level (per-turn; compaction requests unaffected)
}
```
+3 -1
View File
@@ -40,6 +40,7 @@ interface LLMInterface {
interface GenerativeModelParameters {
newMessages: OmniMessage[]; // 仅本轮新增消息(实现自行维护历史,多 role 不接受)
signal?: AbortSignal;
thinkingLevel?: ThinkingLevelName; // 本次请求的思考等级覆盖;缺省用构造默认值
}
```
@@ -78,7 +79,7 @@ interface GenerativeModelConfig {
systemPrompt?: string; // 占位符替换完成后的完整系统提示词
contextWindow?: number;
maxTokens?: number;
thinkingLevel?: ThinkingLevelName; // "none" | "low" | "medium" | "high" | "xhigh"
thinkingLevel?: ThinkingLevelName; // 构造期默认档位(逐请求参数可覆盖);"none" | "low" | "medium" | "high" | "xhigh"
requestTimeoutMs?: number; // 单次 Request 超时,默认 120000;<=0 关闭
toolCallIds?: ToolCallIdAllocator; // Session 级 tool_call_id 唯一性登记表(压缩重建时传同一实例)
}
@@ -179,6 +180,7 @@ session.run(
interface RunOptions {
signal?: AbortSignal; // 中断信号(如 Ctrl-C)
approve?: ApproveFn; // 逐工具审批;未注入时默认全部拒绝
thinkingLevel?: ThinkingLevelName; // 本次 run 的思考等级(逐轮参数;压缩请求不受影响)
}
```
+2 -2
View File
@@ -79,11 +79,11 @@ Some models in the preset catalog: deepseek-v4-pro / deepseek-v4-flash, gemini-3
## Thinking levels
Five levels: `none | low | medium | high | xhigh`, configured per Agent as `model.thinking_level` in `system_config.yaml`, default medium. The Web pickers offer `low` and above only (many models cannot disable thinking; `none` stays a valid stored value and still displays). The chat draft view offers a quick picker next to the model selector: a picked level is written back to the selected Agent's setting immediately (the switched-to level becomes that Agent's new default and applies from the next session; a running session keeps the level it was created with). See [Configuration](/configuration).
Five levels: `none | low | medium | high | xhigh`, configured per Agent as `model.thinking_level` in `system_config.yaml`, default medium. The Web pickers offer `low` and above only (many models cannot disable thinking; `none` stays a valid stored value and still displays). The chat draft view offers a quick picker next to the model selector: a picked level is written back to the selected Agent's setting immediately (the switched-to level becomes that Agent's new default and applies from the next session). Inside an active session the thinking level is a **per-turn parameter**: the composer's picker lists only the levels and starts out showing the Agent config's level — while the user hasn't picked one it auto-follows the config (sends omit the level, so config edits keep taking effect); once picked, the level sticks for that session and rides on every subsequent send (it applies to that session's subsequent Tasks only and never writes back to the Agent config). See [Configuration](/configuration).
## Models decoupled from Agents
An Agent never binds a model: the model is chosen when a Session is created and stays locked for that Session; the same Agent can run different Sessions on different models. The three `pricing` buckets feed the usage/cost center's per-Token accounting.
An Agent never binds a model: the model is chosen when a Session is created and stays locked for that Session; the same Agent can run different Sessions on different models. The in-session `/model` command changes models handoff-style: it opens a new session for the same Agent on the new model, keeping the current Workspace, whose first message carries a `<model_switch_from>` source block (the source session id and its Trace file path) — the history is not injected into the new context (some models require thinking payloads and `fidelity` on history replay, which cannot cross models); the model reads the Trace file itself when it needs it, and the source session stays untouched. The three `pricing` buckets feed the usage/cost center's per-Token accounting.
Credential handling:
+2 -2
View File
@@ -79,11 +79,11 @@ api_key = "sk-..."
## 思考等级
思考等级共五档:`none | low | medium | high | xhigh`,按 Agent 在 `system_config.yaml` 的 `model.thinking_level` 配置,默认 medium。Web 拾取器只提供 `low` 及以上档位(多数模型不支持关闭思考;`none` 仍是合法的已存值,能正常显示)。对话草稿页在模型选择器旁提供快捷拾取器:选定档位立即写回所选 Agent 的该项配置(切换后的档位即成为该 Agent 的新默认,自下一个 Session 生效;进行中的 Session 沿用创建时的档位)。见 [配置参考](/configuration)。
思考等级共五档:`none | low | medium | high | xhigh`,按 Agent 在 `system_config.yaml` 的 `model.thinking_level` 配置,默认 medium。Web 拾取器只提供 `low` 及以上档位(多数模型不支持关闭思考;`none` 仍是合法的已存值,能正常显示)。对话草稿页在模型选择器旁提供快捷拾取器:选定档位立即写回所选 Agent 的该项配置(切换后的档位即成为该 Agent 的新默认,自下一个 Session 生效)。进行中的会话里,思考等级是**逐轮参数**:输入区拾取器只列出各档位,初始即显示 Agent 配置的档位——用户未手动选择时自动跟随配置下发(请求不携带档位,配置的修改持续生效);选定某档后即固定为该会话的档位,随之后每次发送携带(仅作用于该会话的后续 Task,不写回 Agent 配置)。见 [配置参考](/configuration)。
## 模型与 Agent 解耦
Agent 从不绑定模型:模型在创建 Session 时选定,并在该 Session 内锁定不变;同一个 Agent 可以在不同 Session 用不同模型运行。`pricing` 三档价格供用量/成本中心按 Token 计费。
Agent 从不绑定模型:模型在创建 Session 时选定,并在该 Session 内锁定不变;同一个 Agent 可以在不同 Session 用不同模型运行。会话内的 `/model` 命令按 handoff 方式换模型:在同一 Agent 下新建一个使用新模型、沿用当前 Workspace 的会话,首条消息携带 `<model_switch_from>` 源块(源会话 id 与其 Trace 文件路径)——历史不注入新上下文(部分模型回放历史时必须携带 thinking 与 `fidelity`,不能跨模型),模型需要时按路径自行读取;原会话保持不变。`pricing` 三档价格供用量/成本中心按 Token 计费。
凭证处理:
+1 -2
View File
@@ -38,7 +38,6 @@ interface SessionMetaPayload {
model_context_window: number | string;
system_prompt: string; // fully assembled, placeholders substituted
tools: ToolDefinition[]; // the complete tool schema sent to the model
thinking_level: string; // "default" when unconfigured
agent_state: string; // absolute path of the Agent State
workspace: string; // absolute path of the Workspace
source?: "subagent" | "schedule"; // session origin; absent = user-created
@@ -51,7 +50,7 @@ interface ToolDefinition {
}
```
On resume, the engine takes this Trace line as the runtime config — the model, system prompt and Workspace are immutable for the Session's lifetime. See [Sessions & Traces](/sessions-and-traces).
session_meta holds **per-session invariants only** — the model, system prompt and Workspace are immutable for the Session's lifetime; on resume, the engine takes this Trace line as the runtime config. See [Sessions & Traces](/sessions-and-traces). The thinking level is a per-turn parameter (sent with each Task) and is not recorded here; legacy Traces may still carry a `thinking_level` field in their meta, which resume keeps honoring for back-compat.
## model_msg: complete payloads
+1 -2
View File
@@ -38,7 +38,6 @@ interface SessionMetaPayload {
model_context_window: number | string;
system_prompt: string; // 占位符替换完成后的完整系统提示词
tools: ToolDefinition[]; // 发给模型的完整工具 schema
thinking_level: string; // 未配置时为 "default"
agent_state: string; // Agent State 绝对路径
workspace: string; // Workspace 绝对路径
source?: "subagent" | "schedule"; // Session 来源;缺省 = 用户创建
@@ -51,7 +50,7 @@ interface ToolDefinition {
}
```
恢复 Session 时,引擎直接以 Trace 中的这条消息为运行时配置——模型、系统提示词、Workspace 在 Session 生命周期内不可变,见 [Session 与 Trace](/sessions-and-traces)。
session_meta 只承载**会话级不变量**——模型、系统提示词、Workspace 在 Session 生命周期内不可变;恢复 Session 时引擎直接以 Trace 中的这条消息为运行时配置,见 [Session 与 Trace](/sessions-and-traces)。思考等级是逐轮参数(随每次 Task 下发),不记录在此;旧版 Trace 的 meta 里可能仍带 `thinking_level` 字段,恢复时按兼容逻辑继续生效。
## model_msg:完整消息
+7 -2
View File
@@ -139,12 +139,12 @@ The paths below omit the `/api/sessions/:sessionId` prefix. For the storage mode
| Method | Path | Description |
| --- | --- | --- |
| GET | / | Session info |
| GET | / | Session info (the single-session GET additionally carries `tracePath`, the absolute path of the latest Trace file; list rows omit it) |
| PATCH | / | Update: `{approvalMode?, archived?, title?}` |
| DELETE | / | Delete the Session (along with its Traces and scratch files) |
| GET | /messages | Full OmniMessage history |
| GET | /stream | SSE event stream (next section) |
| POST | /tasks | Start a Task: `{input: TaskInputPart[]}` → 202 |
| POST | /tasks | Start a Task: `{input: TaskInputPart[], thinkingLevel?}` → 202 |
| POST | /approvals/:toolCallId | Approval decision: `{decision}` is `allow` or `deny` → 204 |
| POST | /abort | Interrupt the current Task: 202 when triggered, 204 when idle |
| POST | /compact | Trigger context compaction: 202; 409 `nothing_to_compact` when there is nothing to compact |
@@ -194,6 +194,9 @@ Key request bodies (explicit keys):
// POST /api/sessions/:sessionId/tasks — start a Task
interface TaskCreateRequest {
input: TaskInputPart[];
// Thinking level for this Task (a per-turn parameter, one of the five names; 400 otherwise);
// omitted = falls back to the Agent config
thinkingLevel?: "none" | "low" | "medium" | "high" | "xhigh";
}
type TaskInputPart =
| { type: "text"; text: string }
@@ -205,6 +208,8 @@ interface ApprovalDecisionRequest {
}
```
The Web's `/model` switch has no dedicated endpoint: like the @ handoff, it composes the ordinary APIs above — session creation opens a new Session for the same Agent (the chosen model, the source Workspace carried over), then POST /tasks sends a first message opening with a `<model_switch_from>` source block (the source session id, its `tracePath`, the Workspace, and the previous model pair); the model reads that Trace file itself when it needs the earlier history.
## Streaming (SSE)
Real-time delivery uses Server-Sent Events, not WebSocket, on two channels (the ordering semantics of what the channels carry are on [Message Flow & Ordering](/message-flow)):
+6 -2
View File
@@ -139,12 +139,12 @@ Schedule 写操作仅限 Owner。新建 Session 模式的任务,`modelId` 与
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | / | Session 信息 |
| GET | / | Session 信息(单会话 GET 额外携带 `tracePath`:最新 Trace 文件的绝对路径;列表行不含) |
| PATCH | / | 更新:`{approvalMode?, archived?, title?}` |
| DELETE | / | 删除 Session(连同 Trace 与暂存文件) |
| GET | /messages | 完整 OmniMessage 历史 |
| GET | /stream | SSE 事件流(见下节) |
| POST | /tasks | 发起 Task:`{input: TaskInputPart[]}` → 202 |
| POST | /tasks | 发起 Task:`{input: TaskInputPart[], thinkingLevel?}` → 202 |
| POST | /approvals/:toolCallId | 审批决定:`{decision}` 取 `allow` 或 `deny` → 204 |
| POST | /abort | 中断当前 Task:已触发返回 202,无任务返回 204 |
| POST | /compact | 触发上下文压缩:202;无可压缩内容返回 409 `nothing_to_compact` |
@@ -194,6 +194,8 @@ GET /preview/<token>/<相对路径> (不鉴权,令牌即凭证
// POST /api/sessions/:sessionId/tasks —— 发起一个 Task
interface TaskCreateRequest {
input: TaskInputPart[];
// 本次 Task 的思考等级(逐轮参数,五档之一;非法值 400);缺省 = 回退到 Agent 配置的档位
thinkingLevel?: "none" | "low" | "medium" | "high" | "xhigh";
}
type TaskInputPart =
| { type: "text"; text: string }
@@ -205,6 +207,8 @@ interface ApprovalDecisionRequest {
}
```
Web 的 `/model` 模型切换没有专用接口:它按 @ handoff 的方式复用上面的普通接口——先用会话创建接口在同一 Agent 下新建 Session(选定新模型并沿用源 Workspace),再 POST /tasks 发送以 `<model_switch_from>` 源块开头的首条消息(源会话 id、其 `tracePath`、Workspace 与原模型二元组),模型需要早前历史时自行读取该 Trace 文件。
## 流式接口(SSE)
实时通道采用 Server-Sent Events 而非 WebSocket,共两条(通道内承载的消息顺序语义见[消息流转与时序](/message-flow)):
@@ -55,7 +55,7 @@ See `packages/core/src/trace/writer.ts` for the implementation.
The head of a Trace (illustrative; one OmniMessage envelope per line):
```jsonl
{"timestamp":"2026-07-18T03:10:22.531Z","type":"session_meta","payload":{"session_id":"session-2026-07-18-11-10-22-3f8a1c2d","provider":"deepseek","model_id":"deepseek-v4-pro","model_context_window":1000000,"system_prompt":"…","tools":[…],"thinking_level":"medium","agent_state":"/home/u/.penguin/data/default_project/agents/default_agent/agent_state","workspace":"/home/u/work"}}
{"timestamp":"2026-07-18T03:10:22.531Z","type":"session_meta","payload":{"session_id":"session-2026-07-18-11-10-22-3f8a1c2d","provider":"deepseek","model_id":"deepseek-v4-pro","model_context_window":1000000,"system_prompt":"…","tools":[…],"agent_state":"/home/u/.penguin/data/default_project/agents/default_agent/agent_state","workspace":"/home/u/work"}}
{"timestamp":"…","type":"event_msg","payload":{"type":"request_begin"}}
{"timestamp":"…","type":"model_msg","payload":{"type":"text","role":"user","text":"Create hello.txt"}}
{"timestamp":"…","type":"model_msg","payload":{"type":"tool_call","role":"assistant","name":"exec_command","arguments":"{\"cmd\":\"printf hi > hello.txt\"}","tool_call_id":"call_0"}}
@@ -79,6 +79,10 @@ Recovery requires that the Workspace and the model still exist. What recovery gu
Special case: if the latest Trace file ends with a completed compaction, that context is closed as a whole — resume starts from an empty context; in summarize mode the `<context_summary>` is reconstructed and prepended to the first input after resume.
## Model switch (/model)
The Web's `/model` command changes models the way the @ handoff does: it creates a new Session under the same Agent via the ordinary session-creation API (the chosen model, **the source session's Workspace** — so files stay reachable), and the first message opens with a `<model_switch_from>` source block — the source session id, the absolute path of its latest Trace file, the Workspace, and the previous model pair — followed by whatever the user typed. The history is **not injected** into the new context: some models require thinking payloads and `fidelity` byte-for-byte when history is replayed, which cannot cross models — instead the model reads the source Trace file itself (JSONL, one message envelope per line) when it needs the earlier context. The source session and its Trace are untouched.
## Field fidelity
Each content message's opaque provider `fidelity` payload (thinking signatures, phase labels, encrypted reasoning, …) is preserved verbatim in the Trace and sent back verbatim — some models require it byte-for-byte on history replay, and any rewriting would break compatibility. This is one reason the Trace stores raw OmniMessage envelopes rather than a post-processed format.
@@ -55,7 +55,7 @@ Trace 是 append-only 的 JSON Lines 文件,每行一个 OmniMessage 信封(
一条 Trace 的开头(示意,每行一个 OmniMessage 信封):
```jsonl
{"timestamp":"2026-07-18T03:10:22.531Z","type":"session_meta","payload":{"session_id":"session-2026-07-18-11-10-22-3f8a1c2d","provider":"deepseek","model_id":"deepseek-v4-pro","model_context_window":1000000,"system_prompt":"…","tools":[…],"thinking_level":"medium","agent_state":"/home/u/.penguin/data/default_project/agents/default_agent/agent_state","workspace":"/home/u/work"}}
{"timestamp":"2026-07-18T03:10:22.531Z","type":"session_meta","payload":{"session_id":"session-2026-07-18-11-10-22-3f8a1c2d","provider":"deepseek","model_id":"deepseek-v4-pro","model_context_window":1000000,"system_prompt":"…","tools":[…],"agent_state":"/home/u/.penguin/data/default_project/agents/default_agent/agent_state","workspace":"/home/u/work"}}
{"timestamp":"…","type":"event_msg","payload":{"type":"request_begin"}}
{"timestamp":"…","type":"model_msg","payload":{"type":"text","role":"user","text":"创建 hello.txt"}}
{"timestamp":"…","type":"model_msg","payload":{"type":"tool_call","role":"assistant","name":"exec_command","arguments":"{\"cmd\":\"printf hi > hello.txt\"}","tool_call_id":"call_0"}}
@@ -79,6 +79,10 @@ Trace 是恢复的唯一事实来源,没有独立的会话数据库需要与
特殊情形:若最新 Trace 文件以一次完成的压缩收尾,则该上下文已整体关闭——恢复从空上下文开始;summarize 模式下会重建 `<context_summary>` 摘要,前置到恢复后第一轮输入中。
## 模型切换(/model)
Web 的 `/model` 命令按 @ handoff 的方式换模型:用普通的会话创建接口在同一 Agent 下新建一个 Session(选定新模型,**沿用源会话的 Workspace**,文件因此保持可达),首条消息以 `<model_switch_from>` 源块开头——携带源会话 id、其最新 Trace 文件的绝对路径、Workspace 与原模型二元组,用户输入的剩余文字紧随其后。历史**不注入**新上下文:部分模型回放历史时要求 thinking 与 `fidelity` 逐字一致,跨模型注入不可行——模型需要早前上下文时按路径自行读取源 Trace 文件(JSONL,每行一个消息信封)。源会话与其 Trace 不受任何影响。
## 字段保真
内容消息携带的不透明 Provider 保真负载 `fidelity`(思考签名、phase 分段标记、加密推理等)在 Trace 中原样保存、原样回传——部分模型在历史回放时要求该负载逐字一致,任何转写都会破坏兼容性。这也是 Trace 直接存储 OmniMessage 信封而非二次加工格式的原因之一。
+1 -1
View File
@@ -33,7 +33,7 @@ The interface language (中文 / English / system) and theme (light / dark / sys
### Creating a Conversation
A new conversation starts as a draft: pick the Agent, the Workspace (via a server-side directory browser), the approval mode, the model, and the thinking level before sending the first message. The Session is created on first send, and from then on its model and Workspace are locked. Switching the thinking level or the model makes the switched-to value the new default: the level is written back to the selected Agent's `model.thinking_level` immediately, and the picked model carries over as the next conversation's default; a running session keeps the level and model it was created with (the input area shows them read-only).
A new conversation starts as a draft: pick the Agent, the Workspace (via a server-side directory browser), the approval mode, the model, and the thinking level before sending the first message. The Session is created on first send, and from then on its model and Workspace are locked. Switching the thinking level or the model in the draft makes the switched-to value the new default: the level is written back to the selected Agent's `model.thinking_level` immediately, and the picked model carries over as the next conversation's default. Inside an active session the thinking level is a per-turn parameter: the composer's picker starts out showing the Agent config's level and auto-follows it until touched (sends omit the level, so config edits keep taking effect); a pick sticks for the session and rides on every subsequent send (never written back to the Agent config); the model stays locked per session — the `/model` command switches models the way the @ handoff does: it opens a new session for the same Agent on the picked model, keeping the current Workspace, whose first message carries a `<model_switch_from>` source block (source session id, Trace file path, previous model) followed by whatever was left in the composer (an interface-language auto-line when empty). In the new session that block collapses into a "switched model" banner linking back to the source conversation, and the model reads the source Trace file itself when it needs the earlier history.
There are four approval modes: `allow-all`, `deny-all`, `read-only` (only read-only tools pass), and `always-ask`. See [Tools and Approvals](/tools).
+1 -1
View File
@@ -33,7 +33,7 @@ penguin web
### 新建会话
新会话从草稿开始:先选择 Agent、Workspace(服务器端目录浏览器选取)、审批模式、模型与思考等级,再发送第一条消息。Session 在首次发送时才真正创建,此后该会话的模型与 Workspace 即被锁定。切换思考等级或模型时,切换后的值即成为新的默认:思考等级立即写回所选 Agent 的 `model.thinking_level`,所选模型则作为下一个新会话的默认延续;进行中的会话沿用创建时的档位与模型(输入区只读展示)。
新会话从草稿开始:先选择 Agent、Workspace(服务器端目录浏览器选取)、审批模式、模型与思考等级,再发送第一条消息。Session 在首次发送时才真正创建,此后该会话的模型与 Workspace 即被锁定。草稿里切换思考等级或模型时,切换后的值即成为新的默认:思考等级立即写回所选 Agent 的 `model.thinking_level`,所选模型则作为下一个新会话的默认延续。进行中的会话里,思考等级是逐轮参数:输入区拾取器初始显示 Agent 配置的档位并自动跟随(未选择时发送不携带档位,配置修改持续生效),选定后即固定为该会话档位、随每次发送下发(不写回 Agent 配置);模型仍在会话内锁定,改用 `/model` 命令切换模型——与 @ handoff 同一方式:在同一 Agent 下新建一个使用所选模型、沿用当前 Workspace 的会话并跳转,其首条消息携带 `<model_switch_from>` 源块(源会话 id、Trace 文件路径、原模型),输入框剩余文字随之发出(为空时发一句界面语言的自动消息);新会话中该源块折叠为一条“已切换模型”横幅,可点击回到原会话,模型需要早前历史时按路径自行读取源 Trace 文件。
审批模式共四种:`allow-all`(全部放行)、`deny-all`(全部拒绝)、`read-only`(仅放行只读工具)、`always-ask`(每次询问),详见[工具与审批](/tools)。