---
title: Agent-Driven Sessions (External AI Drives an Omniscio Session)
---

# Agent-Driven Sessions (External AI Drives an Omniscio Session)

## What it is

Omniscio lets an external AI agent — Claude.ai in a browser, ChatGPT on your phone, a Claude Code session in another project, or any tool holding the Omniscio bearer token — request a brand-new Omniscio session over HTTP, get your one-time approval through the inbox, and then drive that session through multi-turn dialog under turn and dollar caps. The agent posts the initial prompt, polls for approval, sends follow-ups, reads replies, and ends the session voluntarily — all without you touching the keyboard except for the single approval click.

This is intentionally different from the other two ways Omniscio lets external AIs act on your data. **Recipe agent-triggering** ([agent-trigger-recipes.md](agent-trigger-recipes.md)) is one-time pre-approval: you flip a per-recipe toggle, and any agent can fire that exact recipe forever. **Cron / automation CLI authoring** ([recipes-cli-authoring.md](recipes-cli-authoring.md)) is per-fire approval-gated: every job lands in the inbox before it runs. Agent-driven sessions sit in the middle: **one approval per session creation, then turn-bounded multi-turn dialog with revoke-anytime**. After approval, the agent has up to N follow-up turns and $M of spend before Omniscio stops accepting new turns; you can revoke at any moment from the session header.

**One caller never sees that card: an Overseer.** A worker spawn asked for by a running Overseer (`agentType` set, source `__overseer__:<slot>`) is applied on the spot — standing that Overseer up *was* the approval, so asking again would only put your own decision back in front of you on a card the Overseer can never clear itself. It is still fully attributed, rate-limited and capped. Nothing else changes: an Overseer whose slot you switched off, or a session you paused, gets no exemption, and every other caller still queues for a click. ([overseers.md](overseers.md))

The whole feature is **off by default**. The user opts in at **Settings → CLI Control → Agent-Driven Sessions**. Until that toggle is on, every endpoint described below returns HTTP 503 with `{"ok": false, "error": "agent-driven sessions disabled"}`.

## Where to find it

### User-visible flow

1. **Agent posts a spawn request.** The HTTP call lands as a row in your Omniscio inbox under **CLI Pending Actions**. The card shows the project name, the agent's initial prompt (collapsed if longer than 300 characters), and the proposed caps (e.g. "20 turns / $1.00"). You see Approve and Reject buttons.

2. **You approve (or reject).** On Approve, the dispatcher spawns a normal Omniscio session in that project's working directory, seeded with the agent's initial prompt as the first user turn. On Reject, the agent's status poll returns `{"status": "rejected"}` and the request closes. If you ignore the row for 7 days, it auto-expires and the polling agent gets `{"status": "rejected", "reason": "expired"}`.

3. **Session appears in a new sidebar group.** Approved agent-driven sessions surface in a dedicated **Agent-driven** group at the top of the project sidebar — above the projects list, separate from your normal sessions. Sessions leave the group automatically when they reach a terminal status (paused, ended, error, archived).

4. **Header shows an "Agent driving" pill.** While the agent is in control, the session header shows an accent-colored pill reading "Agent driving (N/M turns)" alongside a red "Revoke control" button. The pill counts every follow-up turn the agent has sent. Once you revoke, the pill flips to a muted "Agent control revoked" badge.

5. **Agent drives the conversation.** Behind the scenes, the agent polls `GET /agent/sessions/:id/messages?since=N` for new replies, then calls `POST /agent/sessions/:id/follow-up` to send the next turn. Omniscio bills exactly like any other session — the cost shows up in your usage bar.

6. **Revoke whenever you want.** Clicking "Revoke control" stops the agent immediately. Subsequent follow-ups return HTTP 403 with `terminal: true` so the agent stops polling. Importantly, **revoke does NOT end the session** — the conversation stays open in `running` state and you can take over manually by typing in the composer.

### Settings reference

All four settings live at **Settings → CLI Control** under the "Agent-driven sessions" subsection. Field names below are the persisted keys in `config.json` — see [src/shared/types.ts](/src/shared/types.ts) for the type definitions.

| Setting                           | Default | Range         | What it does                                                                                                                                                               |
| --------------------------------- | ------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agentDrivenSessionsEnabled`      | `false` | bool          | Master feature flag. When off, every `/agent/sessions/*` endpoint returns HTTP 503. Routes are still wired but inert.                                                      |
| `agentDrivenMaxTurnsPerSession`   | `20`    | 1-200         | Ceiling on follow-up turns for a single session. **Not enforced today** — the value is stored and shown back to you, but nothing stops a session at the count.            |
| `agentDrivenMaxDollarsPerSession` | `1.00`  | 0.01-20 (USD) | Ceiling on cumulative cost per session in dollars. **Not enforced today** — the value is stored and shown back to you, but nothing stops a session at the amount.         |
| `agentDrivenMaxConcurrent`        | `3`     | 1-10          | Global concurrency limit across all active agent-driven sessions. New spawns are refused at dispatch time once this many sessions are running.                             |

## How it behaves

### Caps and safety

- **Per-session turn cap** (1-200, agent picks at spawn time, default 20). Atomic increment on each follow-up. When the cap hits, the next follow-up returns HTTP 429 with `{"error": "cap_reached", "cap": "turn", "limit": N, "used": N}` AND a system message is inserted into the session conversation so you see why the agent stopped.
- **Per-session dollar cap** (1-2000 cents, agent picks at spawn time, default 100 = $1.00). Pre-flight check before each turn; same 429 + system message pattern when hit.
- **Machine-wide concurrency cap** (default 3, configurable 1-10). Enforced at dispatch time — if approving a request would exceed the cap, the spawn is rejected and the inbox row is marked failed.
- **Per-kind subcap on the inbox queue** — at most 3 simultaneously-pending agent-spawn requests. The 4th returns HTTP 429 with `too many pending agent-spawn requests`.
- **Per-token rate limit** — 10 spawn requests per trailing 60 minutes, per bearer-token hash. Sliding window, returns HTTP 429 with a `Retry-After` header when hit.
- **In-flight serialization** — at most one follow-up per session at a time. Concurrent calls get HTTP 409 with `turn_in_flight`. Lock auto-releases after 60 seconds as a safety net.
- **Body size caps** — initial prompt 100 KB, follow-up prompt 32 KB. Oversize returns HTTP 413.

### Revoke behavior

Clicking "Revoke control" on the session header sets `agent_driven_sessions.revoked_at` to the current timestamp. From that moment forward:

- **Follow-ups return HTTP 403** with `{"error": "revoked", "terminal": true}`. The `terminal: true` flag is the agent's signal to stop polling — there is no recovery path; the user must spawn a fresh session if they want the agent back.
- **Messages endpoint still works** but returns `revoked: true` and `terminal: true` so the agent can drain any final replies before exiting.
- **The session stays in `running` state.** Revoke does NOT pause or archive the session. You keep the conversation, take it over manually by typing in the composer, or archive it yourself when you're done.
- **The header pill flips** from the accent-colored "Agent driving" + red "Revoke control" pair to a muted gray "Agent control revoked" badge.

The "revoke ≠ end" split is deliberate. End is for the agent to release voluntarily ("I'm done"); revoke is for you to override ("I'll take it from here"). Both leave the conversation history intact.

### Error codes

| Status | `error` field                    | Meaning                                                                                                                       | What the agent should do                                                                                                                     |
| ------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `validation`                     | Body failed Zod validation, OR `projectId` is unknown.                                                                        | Read the `error` / `detail` fields. Fix and retry.                                                                                           |
| 400    | (no source session)              | `requireSpawnSourceSession` is on (default) and no `X-AMC-Source-Session-Id` header was sent. The `error` text names the fix. | Send the header (`$AMC_SESSION_ID`), or have the user turn off "Require a source session for command-line spawns" in Settings → CLI Control. |
| 401    | `Unauthorized`                   | Missing or wrong bearer token.                                                                                                | Re-read `~/.amc/cli-token`. If still failing, tell the user to regenerate via Settings → CLI Control.                                        |
| 403    | `revoked`                        | Follow-up against a session the user revoked. Response includes `terminal: true`.                                             | Stop polling. There is no recovery — the user must initiate a new spawn.                                                                     |
| 404    | `not_found`                      | Unknown sessionId or requestId.                                                                                               | Don't retry. The session may have been archived or never existed.                                                                            |
| 409    | `session_state`                  | Follow-up against a session in a terminal state (paused / ended / error / archived).                                          | Don't retry. The session is closed.                                                                                                          |
| 409    | `turn_in_flight`                 | Another follow-up is already running for this session.                                                                        | Wait briefly (1-2 s) and retry — the lock auto-releases when the prior turn completes.                                                       |
| 409    | (queue full)                     | Total pending CLI actions hit the global cap (covers cron + automation + sessions + everything).                              | Tell the user to clear the inbox queue.                                                                                                      |
| 413    | `body_too_large`                 | Initial prompt > 100 KB or follow-up prompt > 32 KB.                                                                          | Trim the prompt and retry.                                                                                                                   |
| 429    | `rate_limited`                   | More than 10 spawn requests in the trailing 60 minutes for this token. Includes `Retry-After` header.                         | Honor the header and back off.                                                                                                               |
| 429    | `cap_reached`                    | Turn or dollar cap hit. Response includes `cap: 'turn'\|'dollar'`, `limit`, `used`.                                           | Stop sending follow-ups. The user must extend the cap or revoke and respawn.                                                                 |
| 429    | (per-kind subcap)                | More than 3 pending agent-spawn requests in the inbox. An Overseer's spawns never queue, so they never count toward this cap.  | Tell the user to approve or reject existing rows first.                                                                                      |
| 503    | `agent-driven sessions disabled` | Feature flag is off.                                                                                                          | Tell the user to enable it at Settings → CLI Control → Agent-Driven Sessions.                                                                |

## For agents

### How to use it (HTTP)

All five endpoints require the Omniscio bearer token. Read it from `~/.amc/cli-token` (Windows: `%USERPROFILE%\.amc\cli-token`). The auth pattern matches the rest of CLI Control — see [the omniscio-control skill](/.claude/skills/omniscio-control/SKILL.md).

#### 1. Enqueue a spawn request

```bash
TOKEN=$([ -n "$AMC_CLOUD_SESSION_BOX" ] && printf '%s' "$AMC_CLI_TOKEN" || cat ~/.amc/cli-token)
curl -s -X POST http://127.0.0.1:19519/agent/sessions \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-AMC-Source-Session-Id: $AMC_SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "proj_abc123",
    "initialPrompt": "Audit the imports in src/ for unused exports and report findings.",
    "turnCap": 20,
    "dollarCapCents": 100
  }'
```

The `X-AMC-Source-Session-Id` header identifies the session making the request (Omniscio-spawned agents have their id in `$AMC_SESSION_ID`), so the spawned session links back to its origin with a "Spawned by" note. It is **required by default**: with the `requireSpawnSourceSession` setting on (the default), a spawn with no source is refused with HTTP `400`. A non-session caller must send a real session id or have the user turn that setting off in Settings → CLI Control.

Response (HTTP 200):

```json
{ "ok": true, "requestId": "cli_pending_xyz", "status": "pending" }
```

Body fields:

- **`projectId`** (required) — UUID of the Omniscio project the session will run in. Get it from `GET /status`.
- **`initialPrompt`** (required) — the first user turn. Capped at 100 KB.
- **`turnCap`** (optional, integer, 1-200, default `20`) — hard ceiling on follow-up turns. The agent gets this many turns total.
- **`dollarCapCents`** (optional, integer, 1-2000, default `100`) — hard ceiling on cumulative cost in cents. 100 = $1.00.

#### 2. Poll for approval status

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:19519/agent/sessions/request/cli_pending_xyz
```

Three response shapes:

```json
{ "ok": true, "status": "pending" }
```

```json
{ "ok": true, "status": "approved", "sessionId": "ses_abc789" }
```

```json
{ "ok": true, "status": "rejected", "reason": "rejected by user" }
```

Once you see `approved`, capture the `sessionId` and switch to the follow-up + messages endpoints. Poll politely — every 2-5 seconds is fine.

#### 3. Send the next turn

```bash
curl -s -X POST http://127.0.0.1:19519/agent/sessions/ses_abc789/follow-up \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Now check tests/ for skipped specs." }'
```

Response (HTTP 200):

```json
{ "ok": true, "turnsUsed": 3 }
```

Only one follow-up may be in flight at a time. A second concurrent caller gets HTTP 409 with `{"error": "turn_in_flight"}`.

#### 4. Read new messages

```bash
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:19519/agent/sessions/ses_abc789/messages?since=0"
```

Response (HTTP 200):

```json
{
  "ok": true,
  "messages": [{ "rowid": 12, "source": "agent", "text": "...", "createdAt": "..." }],
  "cursor": 12,
  "sessionState": "running",
  "revoked": false,
  "terminal": false,
  "turnsUsed": 3,
  "dollarsUsedCents": 47
}
```

`since` is a rowid cursor — pass back the prior response's `cursor` value on the next poll to get only newer messages. The query caps at 500 rows per call. `terminal: true` means the agent should stop polling: it's set when the user has revoked control or when the session reached `ended` / `error` / `archived` status. (A `paused` session is not terminal here — the user can resume it, so polling should continue. Follow-up sends WILL fail with HTTP 409 `session_state` while paused; resume the polling loop without burning a turn.)

#### 5. End the session voluntarily

```bash
curl -s -X POST http://127.0.0.1:19519/agent/sessions/ses_abc789/end \
  -H "Authorization: Bearer $TOKEN"
```

Response (HTTP 200): `{ "ok": true }`. Routes through Omniscio's archive flow — the session moves to archived status and leaves the active sidebar.

### Where to look in code

For agents working inside the Omniscio repo:

- HTTP routes — [src/main/services/cli/cli-server-agent-routes.ts](/src/main/services/cli/cli-server-agent-routes.ts)
- Service (caps, in-flight lock, rate limiter) — [src/main/services/agent-driven-session.ts](/src/main/services/agent-driven-session.ts)
- Dispatcher arm (approve → spawn) — [src/main/services/cli/cli-pending-dispatcher.ts](/src/main/services/cli/cli-pending-dispatcher.ts) (search `session.spawn.agent_driven`)
- Zod schemas — [src/shared/ipc-schemas.ts](/src/shared/ipc-schemas.ts) (`agentDrivenSpawnPayloadSchema`, `agentDrivenFollowUpSchema`)
- DB queries + table — [src/main/db/queries-agent-driven.ts](/src/main/db/queries-agent-driven.ts), migration v129 in [src/main/db/incremental-migrations.ts](/src/main/db/incremental-migrations.ts) (search `agent_driven_sessions`)
- Inbox approval pane — [src/renderer/src/features/cli-pending/AgentDrivenSpawnApprovalPane.tsx](/src/renderer/src/features/cli-pending/AgentDrivenSpawnApprovalPane.tsx)
- Sidebar group — [src/renderer/src/features/dashboard/ProjectsSidebar.tsx](/src/renderer/src/features/dashboard/ProjectsSidebar.tsx) (search `Agent-driven`)
- Header chip — [src/renderer/src/features/sessions/AgentDrivenHeaderChip.tsx](/src/renderer/src/features/sessions/AgentDrivenHeaderChip.tsx)
- Settings UI — [src/renderer/src/features/settings/CliControlSettings.tsx](../../src/renderer/src/features/settings/sections/cli-control/CliControlSettings.tsx)
- Settings types + defaults — [src/shared/types.ts](/src/shared/types.ts) (search `agentDriven`)

## Related

- [agent-trigger-recipes.md](agent-trigger-recipes.md) — pre-approved recipes (one toggle, agent fires forever)
- [recipes-cli-authoring.md](recipes-cli-authoring.md) — recipe authoring via inbox approval (per-fire gate)
- [cli-control.md](cli-control.md) — full CLI Control Server endpoint reference
- [cli-pending-actions.md](cli-pending-actions.md) — the inbox queue these spawn requests live in
- [omniscio-control skill](/.claude/skills/omniscio-control/SKILL.md) — bearer-token auth contract
