feat(server): add background agents

An agent can be created background in any workspace. It stays a durable,
addressable agent, but default discovery, workspace status and attention
leave it out. Effective background is agent.background ||
workspace.background.

- Protocol: `create_agent_request.background` now marks the agent itself
  and stays the workspace intent for a workspace the request creates.
  Snapshots carry `background`. The daemon advertises
  `features.backgroundAgents`. Clients advertise the `background_agents`
  capability.
- Daemon: the flag persists in the agent record, so restart and resume
  keep it. AgentDirectory owns visibility. Inclusive reads and
  subscriptions from sources without `background_agents` see only agents
  in background workspaces, because older apps would otherwise open a tab
  for each one.
- Inheritance: omitted on agent-scoped creation, children and new
  workspaces take the caller agent's background.
- MCP: create_agent accepts `background`.
- SDK: placing a background agent in an existing workspace, or under a
  caller, fails fast with "Update the host to use background agents." on
  older hosts.
- CLI: `ls -a` and `ls --background` add a BACKGROUND column.
This commit is contained in:
Mohamed Boudra
2026-11-03 13:38:08 +01:00
parent eb8b5b8f94
commit 129102d5c6
30 changed files with 823 additions and 139 deletions
@@ -51,6 +51,10 @@ export const CLIENT_CAPS = {
pluginBinaryPayloads: "plugin_binary_payloads",
// COMPAT(workspaceSetupBlocked): added in v0.8.0, remove after 2027-03-07 once client floor >= v0.8.0.
workspaceSetupBlocked: "workspace_setup_blocked",
// COMPAT(backgroundAgents): added in v0.15.0, remove after 2027-05-03 once client floor >= v0.15.0.
// Sources without it never receive agent-level background agents in inclusive lists or
// update subscriptions, because older apps auto-open a tab for every replicated root agent.
backgroundAgents: "background_agents",
browserHost: "browser_host",
} as const;
+8 -1
View File
@@ -891,6 +891,9 @@ export const AgentSnapshotPayloadSchema = z.object({
providerUnavailable: z.boolean().optional(),
// Private helper marker, retained for exact-ID snapshots and older wire messages.
internal: z.boolean().optional(),
// Background agent: hidden from default discovery, tabs, tracks and notifications.
// Fixed at creation. Effective background also includes the workspace's flag.
background: z.boolean().optional(),
});
export type AgentSnapshotPayload = z.infer<typeof AgentSnapshotPayloadSchema>;
@@ -1802,7 +1805,8 @@ export const CreateAgentRequestMessageSchema = z.object({
// in the config schema, which is reused for updates. Retained for wire parsing;
// public internal creation is no longer supported.
internal: z.boolean().optional(),
// Workspace creation intent, rejected when selecting an existing workspace.
// The created agent is a background agent. A workspace this request creates inherits it.
// Omitted on agent-scoped creation, the caller agent's own value applies.
background: z.boolean().optional(),
labels: z.record(z.string(), z.string()).default({}),
requestId: z.string(),
@@ -3694,6 +3698,8 @@ export const ServerInfoStatusPayloadSchema = z
// COMPAT(internalWorkspaces): added in v0.9.0; remove gate after 2027-03-17.
internalWorkspaces: z.boolean().optional(),
backgroundWorkspaces: z.boolean().optional(),
// COMPAT(backgroundAgents): added in v0.15.0; remove gate after 2027-05-03.
backgroundAgents: z.boolean().optional(),
providersSnapshot: z.boolean().optional(),
connectedHosts: z.boolean().optional(),
hostMultiplexing: z.boolean().optional(),
@@ -7802,6 +7808,7 @@ export const WSHelloMessageSchema = z.object({
[CLIENT_CAPS.providerSnapshotReferences]: z.boolean().optional(),
[CLIENT_CAPS.timelineReplacementInvalidation]: z.boolean().optional(),
[CLIENT_CAPS.timelineNotifications]: z.boolean().optional(),
[CLIENT_CAPS.backgroundAgents]: z.boolean().optional(),
[CLIENT_CAPS.browserHost]: BrowserAutomationHostCapabilitySchema.optional(),
})
.passthrough()