feat(app): hide background agents from tabs, track and status

This commit is contained in:
Mohamed Boudra
2026-11-03 13:51:36 +01:00
parent 129102d5c6
commit 765851cc69
26 changed files with 329 additions and 58 deletions
+30 -11
View File
@@ -183,23 +183,42 @@ Some providers can create their own child sessions inside one provider runtime.
The provider still owns the underlying runtime. Paseo keeps an agent record so the child can be opened, tracked, archived, and cascaded with the parent, but prompts and history hydration route through the provider adapter for that native child handle.
## Background workspaces
## Background visibility
`workspace.background` controls discovery only. Background workspaces contain ordinary durable
agents and terminals: provider history, plugin hooks, parentage, archive, and restart recovery
keep their normal behavior. Visibility is creation-only; selecting an existing workspace cannot
change it. New workspaces default to false, inherit the caller workspace's value when an agent
creates one without an override, and accept explicit true or false.
An agent is effectively background when it or its workspace is background:
`agent.background || workspace.background`. Background controls discovery only. Background agents
and the agents in background workspaces are ordinary durable agents: provider history, plugin
hooks, parentage, finish notifications to a caller, archive, and restart recovery keep their
normal behavior. Both flags are fixed at creation.
The daemon derives agent discovery from workspace ownership. Default workspace and agent lists,
including History requests, exclude background work; `filter.includeBackground` includes it.
Exact-ID operations bypass discovery filtering. The app replicates both kinds and filters only
its sidebar with **Show background**, so direct navigation and restored tabs remain available.
- **Agent.** `create_agent_request.background` marks the agent. Use it for helpers the user does
not watch, such as a plugin's classifier in the user's open workspace. A background agent never
raises attention, adds nothing to its workspace's status bucket or Last prompt time, and stays
out of tabs, the parent's subagent track and agent lists in the app. A tab opened by id still
works.
- **Workspace.** `workspace.background` hides the workspace from the sidebar unless **Show
background** is on.
- **Inheritance.** When `background` is omitted, an agent created by a caller agent takes the
caller's own `background`. A new workspace takes the caller's effective background. A request
that creates its workspace applies `background` to both the agent and the workspace. Explicit
true or false always wins.
`AgentDirectory` (`packages/server/src/server/agent/directory.ts`) owns visibility for lists.
Default workspace and agent lists, including History requests, exclude background work;
`includeBackground` includes it. Exact-ID operations bypass discovery filtering. The app
replicates inclusively and filters at each surface.
Sources without the `background_agents` hello capability get workspace-only visibility on
inclusive reads and subscriptions: agents in background workspaces, but no agent-level background
agents. Older apps replicate inclusively and auto-open a tab for every root agent, so a classifier
run would otherwise open a tab each turn. That visibility has its own directory sync generation.
Sequenced directories keep complete canonical collections. A filtered read must never replace
those collections with its subset. Inclusive and default observations have distinct cursor
scopes, so changing scope requires a fresh snapshot rather than reusing an incomplete cache.
Clients gate creation and inclusive discovery on `features.backgroundWorkspaces`.
Clients gate background workspaces and inclusive discovery on `features.backgroundWorkspaces`.
They gate a background agent placed in an existing workspace or under a caller on
`features.backgroundAgents`.
### Private daemon helpers
+1
View File
@@ -28,6 +28,7 @@ Authoritative terminology. UI label wins. Don't invent synonyms; use what's here
- **Workspace-owned state** — Per-workspace state that never leaks to a same-`cwd` sibling: tabs, agents, terminals, panes, title, plus review drafts, diff-mode overrides, composer attachments, and file-explorer open/expand state. Keyed by `workspaceId` (`cwd` only as a fallback for old payloads). See [architecture.md](architecture.md#right-sidebar-boundary-directory-backed-vs-workspace-owned).
- **Last prompt** — Sidebar workspace sort by the most recent human prompt recorded by an agent in that workspace, including archived agents. Agent output, agent-to-agent messages, scheduled prompts, and opening a workspace do not count. A workspace without a human prompt uses its creation time.
- **Workspace status bucket** — Aggregate activity signal for a workspace row. Same-`cwd` workspaces intentionally share agent and terminal status buckets, while tab, agent, and terminal visibility remains scoped by `workspaceId`.
- **Background agent** — An agent hidden from default discovery and from app surfaces that announce agents: tabs, the subagent track, agent lists, workspace status and attention. It is otherwise an ordinary durable agent, addressable by id. An agent is effectively background when it or its workspace is background. Fixed at creation. Code: `background` on `AgentSnapshotPayload` and `create_agent_request`. See [agent-lifecycle.md](agent-lifecycle.md#background-visibility). Don't confuse with background work (provider shells and monitors) or the daemon's private internal helpers, which are never public ([agent-lifecycle.md](agent-lifecycle.md#private-daemon-helpers)).
- **Agent session** — One running instance of an agent inside a workspace (one provider, one model, one cwd, one timeline). The conceptual unit; in the UI this opens as a tab. Moving toward this as the canonical term over "Agent". Code: `AgentSnapshotPayload` (`packages/protocol/src/messages.ts:608`).
- **Session** — Two senses: (a) per-client connection to a daemon, internal; (b) user-facing agent session, see **Agent session**. Code: `Session` (`packages/server/src/server/session.ts`) for (a). Don't confuse with: provider-side agent session log.
- **Profile** — Internal name for the persisted shape of a host. Code: `HostProfile` (`packages/app/src/types/host-connection.ts:37`). Never user-facing. Three other things are called "profile" in this codebase, so always qualify: **Agent profile**, **Terminal profile**, and the provider aliases in [custom-providers.md](custom-providers.md). Bare "profile" is forbidden in UI copy.
@@ -0,0 +1,65 @@
import { test } from "../support/fixtures";
import {
expectWorkspaceShownIdle,
finishAndArchiveAgent,
startBackgroundAgent,
} from "../support/helpers/background-agents";
import { openAgentRoute } from "../support/helpers/mock-agent";
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
import {
expectSubagentRowGone,
expectSubagentRowVisible,
openSubagentsTrack,
seedParentWithSubagent,
seedSubagent,
} from "../support/helpers/subagents";
import { expectOnlyWorkspaceAgentTabsVisible } from "../support/helpers/workspace-tabs";
test.describe("Background agents", () => {
let workspace: SeededWorkspace;
test.beforeAll(async () => {
workspace = await seedWorkspace({
repoPrefix: "background-agents-",
title: "Classifier workspace",
});
});
test.afterAll(async () => {
await workspace?.cleanup();
});
test("a background agent runs in an open workspace without announcing itself", async ({
page,
}) => {
const agents = await seedParentWithSubagent(workspace, {
parentTitle: "Feature agent",
childTitle: "Visible helper",
});
await openAgentRoute(page, { workspaceId: agents.workspaceId, agentId: agents.parent.id });
await openSubagentsTrack(page);
await expectSubagentRowVisible(page, agents.child.id);
const classifier = await test.step("a plugin starts a background agent", () =>
startBackgroundAgent(workspace, { parentId: agents.parent.id, title: "Classifier" }));
// A visible child created after the classifier reaches the app on the same stream, so once
// its row shows, the app has processed the classifier too.
const marker = await seedSubagent(workspace, {
parentId: agents.parent.id,
title: "Ordering marker",
});
await test.step("it stays out of the track, the tabs and the sidebar status", async () => {
await expectSubagentRowVisible(page, marker.id);
await expectSubagentRowGone(page, classifier.id);
await expectOnlyWorkspaceAgentTabsVisible(page, [agents.parent.id]);
await expectWorkspaceShownIdle(page, "Classifier workspace");
});
await test.step("the plugin reads its finish and archives it", async () => {
await finishAndArchiveAgent(workspace, classifier.id);
await expectOnlyWorkspaceAgentTabsVisible(page, [agents.parent.id]);
await expectSubagentRowGone(page, classifier.id);
});
});
});
@@ -0,0 +1,48 @@
import { expect, type Page } from "@playwright/test";
import { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
import type { SeededWorkspace } from "./seed-client";
/**
* Starts a background agent the way a plugin helper does: in the workspace, under a parent,
* streaming for about ten seconds. Resolves once the daemon reports it running.
*/
export async function startBackgroundAgent(
workspace: Pick<SeededWorkspace, "client" | "repoPath" | "workspaceId">,
input: { parentId: string; title: string },
): Promise<{ id: string }> {
const agent = await workspace.client.createAgent({
provider: "mock",
cwd: workspace.repoPath,
workspaceId: workspace.workspaceId,
title: input.title,
modeId: "load-test",
model: "ten-second-stream",
initialPrompt: "classify the last turn",
labels: { [PARENT_AGENT_ID_LABEL]: input.parentId },
background: true,
});
await workspace.client.waitForAgentUpsert(agent.id, (snapshot) => snapshot.status === "running");
return { id: agent.id };
}
/** Waits for the agent's turn to end and archives it, as the plugin does with a helper's reply. */
export async function finishAndArchiveAgent(
workspace: Pick<SeededWorkspace, "client">,
agentId: string,
): Promise<void> {
const finished = await workspace.client.waitForFinish(agentId, 60_000);
expect(finished.status).toBe("idle");
await workspace.client.archiveAgent(agentId);
}
export async function expectWorkspaceShownIdle(page: Page, workspaceName: string): Promise<void> {
const row = page.getByRole("button", {
name: new RegExp(`^${escapeRegExp(workspaceName)}(?:,|$)`),
});
await expect(row).toBeVisible({ timeout: 30_000 });
await expect(row.getByTestId("workspace-status-indicator-running")).toHaveCount(0);
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
@@ -118,6 +118,7 @@ export interface SeedDaemonClient {
featureValues?: Record<string, unknown>;
initialPrompt?: string;
labels?: Record<string, string>;
background?: boolean;
}): Promise<{ id: string; status: string }>;
fetchAgents(options?: { scope?: "active" }): Promise<{
entries: Array<{
@@ -76,7 +76,7 @@ export function useAggregatedAgents(options?: {
}
const serverLabel = serverLabelById.get(serverId) ?? serverId;
for (const agent of agents.values()) {
if (!includeArchived && agent.archivedAt) {
if (agent.background || (!includeArchived && agent.archivedAt)) {
continue;
}
const nextAgent: AggregatedAgent = {
+2
View File
@@ -97,6 +97,8 @@ export interface Agent {
attentionReason?: "finished" | "error" | "permission" | null;
attentionTimestamp?: Date | null;
archivedAt?: Date | null;
/** Background agents stay out of tabs, the subagent track and agent lists. Open them by id. */
background?: boolean;
parentAgentId: string | null;
labels: Record<string, string>;
projectPlacement?: ProjectPlacementPayload | null;
+16
View File
@@ -246,6 +246,22 @@ describe("selectSubagentsForParent", () => {
expect(rows.map((row) => row.id)).toEqual(["child-a"]);
});
it("excludes background children from the track", () => {
setAgents([
makeAgent({ id: "parent-a" }),
makeAgent({ id: "child-a", parentAgentId: "parent-a" }),
makeAgent({ id: "background-child", parentAgentId: "parent-a", background: true }),
]);
const rows = selectSubagentsForParent(
useSessionStore.getState(),
{ serverId: SERVER_ID, parentAgentId: "parent-a" },
EMPTY_PENDING_ARCHIVE_IDS,
);
expect(rows.map((row) => row.id)).toEqual(["child-a"]);
});
it("excludes siblings, unrelated agents, and grandchildren", () => {
setAgents([
makeAgent({ id: "parent-a" }),
+1
View File
@@ -82,6 +82,7 @@ export function selectSubagentsForParent(
const parent = getParentAddressFromLabels(agent.labels, serverId);
if (
agent.archivedAt ||
agent.background ||
pendingArchiveIds.has(`${serverId}:${agent.id}`) ||
parent?.serverId !== params.serverId ||
parent.agentId !== params.parentAgentId
@@ -85,6 +85,7 @@ export function projectAgentSnapshot(agent: Agent): AgentSnapshotPayload {
attentionReason: agent.attentionReason ?? null,
attentionTimestamp: agent.attentionTimestamp?.toISOString() ?? null,
archivedAt: agent.archivedAt?.toISOString() ?? null,
...(agent.background ? { background: true } : {}),
};
}
@@ -133,6 +134,7 @@ export function normalizeAgentSnapshot(snapshot: AgentSnapshotPayload, serverId:
attentionReason: snapshot.attentionReason ?? null,
attentionTimestamp,
archivedAt,
background: snapshot.background === true,
parentAgentId,
labels: snapshot.labels,
};
@@ -14,6 +14,7 @@ function agent(input: {
pendingPermissionCount?: number;
archivedAt?: string | null;
parentAgentId?: string | null;
background?: boolean;
}): Agent {
return {
serverId: "host-a",
@@ -61,6 +62,7 @@ function agent(input: {
attentionTimestamp: input.attentionTimestamp ? new Date(input.attentionTimestamp) : null,
archivedAt: input.archivedAt ? new Date(input.archivedAt) : null,
parentAgentId: input.parentAgentId ?? null,
background: input.background ?? false,
labels: {},
};
}
@@ -158,6 +160,33 @@ describe("workspace agent activity index", () => {
);
});
it("does not let a running background agent change root workspace activity", () => {
const index = buildWorkspaceAgentActivityIndex(
new Map([
[
"root",
agent({ id: "root", workspaceId: "workspace-a", updatedAt: "2026-06-01T10:00:00.000Z" }),
],
[
"classifier",
agent({
id: "classifier",
workspaceId: "workspace-a",
status: "running",
updatedAt: "2026-06-01T10:05:00.000Z",
background: true,
}),
],
]),
);
expect(index.get("workspace-a")).toEqual({
agentId: "root",
status: "done",
enteredAt: new Date("2026-06-01T10:00:00.000Z"),
});
});
it("does not let archived or child agents change root workspace activity", () => {
const index = buildWorkspaceAgentActivityIndex(
new Map([
@@ -22,7 +22,12 @@ export function buildWorkspaceAgentActivityIndex(
for (const agent of agents.values()) {
const parentAgent = agent.parentAgentId ? agents.get(agent.parentAgentId) : undefined;
if (agent.archivedAt || !agent.workspaceId || !isWorkspaceRootAgent(agent, parentAgent)) {
if (
agent.archivedAt ||
agent.background ||
!agent.workspaceId ||
!isWorkspaceRootAgent(agent, parentAgent)
) {
continue;
}
@@ -18,6 +18,7 @@ function makeAgent(input: {
archivedAt?: Date | null;
createdAt?: Date;
lastActivityAt?: Date;
background?: boolean;
}): Agent {
const createdAt = input.createdAt ?? new Date("2026-03-04T00:00:00.000Z");
const lastActivityAt = input.lastActivityAt ?? createdAt;
@@ -58,6 +59,7 @@ function makeAgent(input: {
attentionReason: null,
attentionTimestamp: null,
archivedAt: input.archivedAt ?? null,
background: input.background ?? false,
};
}
@@ -131,6 +133,33 @@ describe("workspace agent visibility", () => {
expect(restored.getState().pinnedAgentIdsByWorkspace["srv:ws-b"]).toBeUndefined();
});
it("keeps a background agent active without auto-opening its tab", () => {
const visible = makeAgent({ id: "visible", cwd: "/repo", workspaceId: WORKSPACE_ID });
const classifier = makeAgent({
id: "classifier",
cwd: "/repo",
workspaceId: WORKSPACE_ID,
background: true,
});
const result = deriveWorkspaceAgentVisibility({
workspaceId: WORKSPACE_ID,
sessionAgents: new Map([
[visible.id, visible],
[classifier.id, classifier],
]),
});
expect(result.autoOpenAgentIds).toEqual(new Set([visible.id]));
expect(result.activeAgentIds).toEqual(new Set([visible.id, classifier.id]));
expect(
shouldPruneWorkspaceAgentTab({
agentId: classifier.id,
agentsHydrated: true,
activeAgentIds: result.activeAgentIds,
}),
).toBe(false);
});
it("uses directory ownership before historical details when identifying foreign agents", () => {
const own = makeAgent({ id: "own", cwd: "/repo", workspaceId: WORKSPACE_ID });
const foreign = makeAgent({ id: "foreign", cwd: "/repo", workspaceId: "ws-2" });
@@ -36,11 +36,9 @@ export function deriveWorkspaceAgentVisibility(input: {
continue;
}
if (!agent.archivedAt) {
// A background agent stays active so a tab opened by id isn't pruned, but never auto-opens.
activeAgentIds.add(agent.id);
const parentAgent = agent.parentAgentId
? (sessionAgents?.get(agent.parentAgentId) ?? agentDetails?.get(agent.parentAgentId))
: undefined;
if (isWorkspaceRootAgent(agent, parentAgent)) {
if (shouldAutoOpenAgent({ agent, sessionAgents, agentDetails })) {
autoOpenAgentIds.add(agent.id);
}
}
@@ -57,6 +55,21 @@ export function deriveWorkspaceAgentVisibility(input: {
return { activeAgentIds, autoOpenAgentIds, foreignAgentIds };
}
function shouldAutoOpenAgent(input: {
agent: Agent;
sessionAgents: Map<string, Agent> | undefined;
agentDetails: Map<string, Agent> | undefined;
}): boolean {
const { agent, sessionAgents, agentDetails } = input;
if (agent.background) {
return false;
}
const parentAgent = agent.parentAgentId
? (sessionAgents?.get(agent.parentAgentId) ?? agentDetails?.get(agent.parentAgentId))
: undefined;
return isWorkspaceRootAgent(agent, parentAgent);
}
interface AgentMapsSnapshot {
sessions: Record<
string,
+2 -2
View File
@@ -289,10 +289,10 @@ behavior.
```bash
paseo ls # Non-archived agents in active workspaces
paseo ls -a # Also include archived agents
paseo ls -a # Also include archived and background agents, with a BACKGROUND column
paseo ls -g # Non-archived agents across all workspaces
paseo ls -a -g --json # All agents, including archived, as JSON
paseo ls --background # Also include agents in background workspaces
paseo ls --background # Also include background agents and agents in background workspaces
```
## Streaming output
+2 -2
View File
@@ -270,10 +270,10 @@ paseo plugin remove my-plugin
```bash
paseo ls # Non-archived agents in active workspaces
paseo ls -a # Also include archived agents
paseo ls -a # Also include archived and background agents, with a BACKGROUND column
paseo ls -g # Non-archived agents across all workspaces
paseo ls -a -g --json # All agents, including archived, as JSON
paseo ls --background # Also include agents in background workspaces
paseo ls --background # Also include background agents and agents in background workspaces
```
## 流式输出 {#streaming-output}
+12 -12
View File
@@ -87,18 +87,18 @@ MCP does not expose an agent-detach tool. Detaching is a manual user action in t
### Agents
| Tool | Function |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_agent` | Create an agent, optionally placing it in an existing workspace with `workspaceId`. Use `initialPrompt` to start work; returns without waiting for completion. |
| `send_agent_prompt` | Send a prompt using `agentId` and `prompt`; returns without waiting for completion. |
| `get_agent_status` | Return the latest snapshot for an agent. |
| `list_agents` | List recent agents as compact metadata; `includeBackground: true` includes background work. |
| `cancel_agent` | Abort an agent's current run but keep the agent alive. |
| `archive_agent` | Soft-delete an agent and remove it from the active list. |
| `kill_agent` | Terminate an agent session permanently. |
| `update_agent` | Update an agent name, labels, or runtime settings such as mode/model/thinking/features. |
| `get_agent_activity` | Return recent agent timeline entries as a curated summary. |
| `set_agent_mode` | Switch an agent's session mode. |
| Tool | Function |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `create_agent` | Create an agent, optionally placing it in an existing workspace with `workspaceId`. Use `initialPrompt` to start work; returns without waiting for completion. `background: true` hides it from tabs, default lists, your subagent track and notifications; you still get its finish notification. Omission inherits your own value. |
| `send_agent_prompt` | Send a prompt using `agentId` and `prompt`; returns without waiting for completion. |
| `get_agent_status` | Return the latest snapshot for an agent. |
| `list_agents` | List recent agents as compact metadata; `includeBackground: true` includes background work. |
| `cancel_agent` | Abort an agent's current run but keep the agent alive. |
| `archive_agent` | Soft-delete an agent and remove it from the active list. |
| `kill_agent` | Terminate an agent session permanently. |
| `update_agent` | Update an agent name, labels, or runtime settings such as mode/model/thinking/features. |
| `get_agent_activity` | Return recent agent timeline entries as a curated summary. |
| `set_agent_mode` | Switch an agent's session mode. |
Paseo agents receive finish, error, and permission notifications by default; set `notifyOnFinish: false` to disable them. External MCP clients use `get_agent_status` and `get_agent_activity` to check results.
+12 -12
View File
@@ -79,18 +79,18 @@ MCP 不提供解除 Agent 父子关系的工具。解除关系是用户在应用
### Agent {#agents}
| 工具 | 功能 |
| -------------------- | ---------------------------------------------------------------------------------------------------- |
| `create_agent` | 创建 Agent,可用 `workspaceId` 把它放到已有的工作区。用 `initialPrompt` 开始工作;不等待完成就返回。 |
| `send_agent_prompt` | 用 `agentId` 和 `prompt` 发送提示词;不等待完成就返回。 |
| `get_agent_status` | 返回 Agent 的最新快照。 |
| `list_agents` | 以精简元数据列出最近的 Agent;`includeBackground: true` 会包含后台工作。 |
| `cancel_agent` | 中止 Agent 当前的运行,但保留该 Agent。 |
| `archive_agent` | 软删除 Agent,并把它从活动列表中移除。 |
| `kill_agent` | 永久终止 Agent 会话。 |
| `update_agent` | 更新 Agent 的名称、标签或运行时设置,例如模式、模型、思考选项和功能。 |
| `get_agent_activity` | 以整理后的摘要返回 Agent 最近的时间线条目。 |
| `set_agent_mode` | 切换 Agent 的会话模式。 |
| 工具 | 功能 |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_agent` | 创建 Agent,可用 `workspaceId` 把它放到已有的工作区。用 `initialPrompt` 开始工作;不等待完成就返回。`background: true` 会把它从标签页、默认列表、你的子 Agent 列表和通知中隐藏;你仍会收到它的完成通知。省略时继承你自己的设置。 |
| `send_agent_prompt` | 用 `agentId` 和 `prompt` 发送提示词;不等待完成就返回。 |
| `get_agent_status` | 返回 Agent 的最新快照。 |
| `list_agents` | 以精简元数据列出最近的 Agent;`includeBackground: true` 会包含后台工作。 |
| `cancel_agent` | 中止 Agent 当前的运行,但保留该 Agent。 |
| `archive_agent` | 软删除 Agent,并把它从活动列表中移除。 |
| `kill_agent` | 永久终止 Agent 会话。 |
| `update_agent` | 更新 Agent 的名称、标签或运行时设置,例如模式、模型、思考选项和功能。 |
| `get_agent_activity` | 以整理后的摘要返回 Agent 最近的时间线条目。 |
| `set_agent_mode` | 切换 Agent 的会话模式。 |
Paseo Agent 默认会收到完成、错误和权限通知;设置 `notifyOnFinish: false` 可以关闭它们。外部 MCP 客户端使用 `get_agent_status` 和 `get_agent_activity` 查看结果。
+1 -1
View File
@@ -588,7 +588,7 @@ server.addHook("agent.permission.after", async ({ agent, requestId, resolution }
});
```
Agent hooks exclude the daemon's private disposable helpers. Public background workspaces and their ordinary agents use the same hooks.
Agent hooks exclude the daemon's private disposable helpers. Background agents and the agents in background workspaces use the same hooks.
## Ordering and returned values
+1 -1
View File
@@ -571,7 +571,7 @@ server.addHook("agent.permission.after", async ({ agent, requestId, resolution }
});
```
Agent 钩子不包括 daemon 的私有临时辅助 Agent。公开的后台工作区及其普通 Agent 使用相同钩子。
Agent 钩子不包括 daemon 的私有临时辅助 Agent。后台 Agent 和后台工作区中的 Agent 使用相同钩子。
## 顺序与返回值 {#ordering-and-returned-values}
+22
View File
@@ -130,6 +130,28 @@ use `client.agents.list({ filter: { includeBackground: true } })` to include the
Exact-ID handles work normally. See [workspace creation](./workspaces.md#create-a-fresh-workspace)
for defaults and caller inheritance.
## Run a helper as a background agent
Pass `background: true` to run a helper in a workspace the user may have open. A background agent
opens no tab, stays out of its parent's subagent track and the sidebar status, and raises no
notifications. Default lists leave it out.
```ts
const helper = await client.workspaces.ref(workspaceId).agents.create({
config: { provider: "codex/gpt-5.5" },
prompt: "Summarize the last turn in one line.",
background: true,
});
const result = await helper.waitForFinish();
console.log(result.lastMessage);
await helper.archive();
```
An agent created under a background `parent` is background unless you pass `background: false`.
Placing a background agent in an existing workspace or under a parent requires
`features.backgroundAgents`; on an older host the SDK throws "Update the host to use background
agents." before sending.
## Request structured output
```ts
+17
View File
@@ -124,6 +124,23 @@ const workspace = await client.workspaces.create({
后台 Agent 会保留历史记录,并在 daemon 重启后继续存在。默认的发现结果会隐藏它们;使用 `client.agents.list({ filter: { includeBackground: true } })` 可以包含它们。精确 ID 的句柄可以正常使用。默认值和调用方继承规则见[创建工作区](./workspaces.md#create-a-fresh-workspace)。
## 以后台 Agent 运行辅助任务 {#run-a-helper-as-a-background-agent}
传入 `background: true`,可以在用户可能正打开的工作区中运行辅助 Agent。后台 Agent 不会打开标签页,不会出现在父 Agent 的子 Agent 列表和侧边栏状态中,也不会触发通知。默认列表不包含它。
```ts
const helper = await client.workspaces.ref(workspaceId).agents.create({
config: { provider: "codex/gpt-5.5" },
prompt: "Summarize the last turn in one line.",
background: true,
});
const result = await helper.waitForFinish();
console.log(result.lastMessage);
await helper.archive();
```
在后台 `parent` 下创建的 Agent 默认也是后台 Agent,除非传入 `background: false`。把后台 Agent 放到已有工作区或某个父 Agent 下需要 `features.backgroundAgents`;在较旧的主机上,SDK 会在发送前抛出 "Update the host to use background agents."。
## 请求结构化输出 {#request-structured-output}
```ts
+3 -2
View File
@@ -61,8 +61,9 @@ Creation options include `config`, `cwd`, `parent`, `title`, `prompt`, `env`, `o
`parent` accepts a host-local agent ID, an `AgentAddress` (`{ serverId, agentId }`), or an agent handle. A handle’s `address` keeps its host identity after disconnect. When creating through `hosts.client(serverId)`, pass the parent handle or complete address; the SDK carries parent metadata to the destination.
`background` is workspace creation intent. It cannot accompany an existing `workspaceId`.
See [background workspaces](./workspaces.md) for caller inheritance and inclusive discovery.
`background` creates a [background agent](./agents.md#run-a-helper-as-a-background-agent). A
workspace the call creates is background too. With a `parent` and no value, the parent's own
`background` applies. See [background workspaces](./workspaces.md) for inclusive discovery.
`config` accepts:
+1 -1
View File
@@ -59,7 +59,7 @@ nav: API 参考
`parent` 接受主机本地的 Agent ID、`AgentAddress`(`{ serverId, agentId }`)或 Agent 句柄。句柄的 `address` 在断开连接后仍保留其主机身份。通过 `hosts.client(serverId)` 创建时,请传入父 Agent 句柄或完整地址;SDK 会把父级元数据带到目标主机。
`background` 表示创建工作区的意图。它不能与已有的 `workspaceId` 一起使用。调用方继承规则和包含后台的发现方式见[后台工作区](./workspaces.md)。
`background` 会创建一个[后台 Agent](./agents.md#run-a-helper-as-a-background-agent)。本次调用创建的工作区也是后台工作区。带 `parent` 且省略该值时,沿用父 Agent 自己的 `background`。包含后台的发现方式见[后台工作区](./workspaces.md)。
`config` 接受:
+6 -6
View File
@@ -61,10 +61,10 @@ remain durable, with ordinary history, lifecycle hooks, archive, and exact-ID ac
sidebar offers **Show background** without dropping replicated state.
`background` is creation-only. Omission defaults to false; when `callerAgentId` identifies an agent
creating a new workspace, omission inherits its workspace's value. Explicit false or true overrides
inheritance. Agent creation with `workspaceId` rejects explicit background creation intent; configure
visibility when creating the workspace. SDK/RPC implicit agent creation also accepts `background`
as workspace creation intent, never as an agent property.
creating a new workspace, omission inherits the caller's visibility: true when the caller agent or
its workspace is background. Explicit false or true overrides inheritance. Agent creation that
creates its workspace applies `background` to both the workspace and the agent. To hide one agent
in an existing workspace, see [background agents](./agents.md#run-a-helper-as-a-background-agent).
Creation and inclusive discovery require `features.backgroundWorkspaces`; the client rejects an
unsupported host with an update-host message.
@@ -135,8 +135,8 @@ do {
Background workspaces are left out unless `filter.includeBackground` is true. Use
`client.workspaces.list({ filter: { includeBackground: true } })` or
`client.agents.list({ filter: { includeBackground: true } })`. Workspace descriptors carry
`background`; agents derive visibility from their workspace and have no separate setting.
`client.agents.list({ filter: { includeBackground: true } })`. Workspace and agent snapshots carry
`background`. An agent is hidden when it or its workspace is background.
## Refresh and archive a handle
+2 -2
View File
@@ -54,7 +54,7 @@ const workspace = await client.workspaces.create({
传入 `background: true` 可以让工作区不出现在默认的发现结果中。它的 Agent 和终端仍然是持久的,有正常的历史记录、生命周期钩子、归档和精确 ID 访问。应用侧边栏提供 **显示后台工作区**,不会丢弃已同步的状态。
`background` 只能在创建时设置。省略时默认为 false;当 `callerAgentId` 指明是某个 Agent 在创建新工作区时,省略则继承该 Agent 所在工作区的值。显式的 false 或 true 会覆盖继承。带 `workspaceId` 创建 Agent 时会拒绝显式的后台创建意图;请在创建工作区时配置可见性。SDK/RPC 的隐式 Agent 创建也接受 `background`,但它表示工作区的创建意图,而不是 Agent 的属性。
`background` 只能在创建时设置。省略时默认为 false;当 `callerAgentId` 指明是某个 Agent 在创建新工作区时,省略则继承调用方的可见性:调用方 Agent 或其工作区为后台时即为 true。显式的 false 或 true 会覆盖继承。同时创建工作区的 Agent 创建请求会把 `background` 同时应用到工作区和 Agent。要在已有工作区中隐藏单个 Agent,见[后台 Agent](./agents.md#run-a-helper-as-a-background-agent)。
创建和包含后台工作区的发现都需要 `features.backgroundWorkspaces`;对于不支持的主机,客户端会拒绝并提示更新主机。
@@ -122,7 +122,7 @@ do {
} while (cursor);
```
除非 `filter.includeBackground` 为 true,否则后台工作区不会被列出。使用 `client.workspaces.list({ filter: { includeBackground: true } })` 或 `client.agents.list({ filter: { includeBackground: true } })`。工作区描述符带有 `background`;Agent 的可见性来自它所在的工作区,没有单独的设置。
除非 `filter.includeBackground` 为 true,否则后台工作区不会被列出。使用 `client.workspaces.list({ filter: { includeBackground: true } })` 或 `client.agents.list({ filter: { includeBackground: true } })`。工作区和 Agent 快照都带有 `background`。Agent 本身或其工作区为后台时,Agent 会被隐藏。
## 刷新和归档句柄 {#refresh-and-archive-a-handle}