Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

> Append-only session log. Read at session start. Update at session end.

## 2026-07-29 — Session-scoped MCP Delivery lifecycle

- Separated generic MCP connection presence from WorkSession lifecycle
reconciliation. A tool call can confirm the endpoint without clearing stale
recovery requests for every Delivery session that endpoint owns.
- Added owner-scoped `workSessions.abandon` so direct MCP clients can
explicitly release a no-output code session without inventing merged,
released, or deployed evidence.
- Made direct MCP Execute completion fail closed when its active code session
has neither an attached implementation PR nor explicit abandonment.
- Added lifecycle next-action guidance to MCP claim/run responses and expanded
operator documentation for shared connections, heartbeats, PR attachment,
abandonment, and completion ordering.
- Added integration coverage for quiet-request churn, multiple Delivery
sessions sharing one MCP connection, explicit abandonment, and guarded run
completion.

## 2026-07-28 — v0.31.0 release preparation

Prepared the serialized minor release for AXI-164 after implementation PR #92
Expand Down
69 changes: 38 additions & 31 deletions docs/agents/providers-and-transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ available, and how chat is served — and why a Codex CLI session must **not**

There are two independent axes:

- **Tier** — *how reachable and how rich* the connection is (presence +
- **Tier** — _how reachable and how rich_ the connection is (presence +
transport). First-class managed runtime → session CLI → basic webhook.
- **Engine** — *who owns the agent loop* for a chat turn: **Runs** (the
- **Engine** — _who owns the agent loop_ for a chat turn: **Runs** (the
runtime) or **Streaming/Completions** (Forge). Orthogonal to tier; see
[Chat & Dispatch Engines](./engines.md).

Expand All @@ -35,23 +35,30 @@ agent's work to the credential owner.
Availability is interpreted from the connection's declared liveness model,
not from its provider name:

| Connection | Positive signals | Silence means |
|---|---|---|
| Managed runtime | Runtime heartbeat, run events, provider state | A confirmed stall is possible after the workspace threshold |
| MCP client | MCP initialize/session, tool calls, explicit lease heartbeat | Quiet / status unconfirmed; never a confirmed stall from silence alone |
| Webhook | Durable delivery plus acknowledgement | Delivery failed or acknowledgement missing |
| On-demand | Successful probe when invoked | Not currently running; global online/offline is not meaningful |
| Connection | Positive signals | Silence means |
| --------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Managed runtime | Runtime heartbeat, run events, provider state | A confirmed stall is possible after the workspace threshold |
| MCP client | MCP initialize/session and tool calls confirm the connection; explicit session heartbeat confirms only that Delivery lease | Quiet / status unconfirmed; never a confirmed stall from silence alone |
| Webhook | Durable delivery plus acknowledgement | Delivery failed or acknowledgement missing |
| On-demand | Successful probe when invoked | Not currently running; global online/offline is not meaningful |

Git commits, pull-request changes, checks, and reviews count as work evidence,
but do not claim that a client process is alive. Operator surfaces show both
the latest lifecycle signal and the latest external work evidence, along with
the confidence of the resulting state.

Connection presence is not inherited by the WorkSessions that connection owns.
A generic MCP call can confirm that the endpoint is reachable, but it neither
renews nor clears recovery for any code-delivery session. Agents heartbeat the
exact session at meaningful phases, attach its PR, or explicitly hand off or
abandon it. Direct Execute runs with an unbound active code session must settle
that session before `runs.complete`.

## Tier 1 — First-class agents (managed runtimes)

The agent is a **full workspace member**: always-on presence, realtime chat,
orchestration, issues, and dispatched work. Forge holds **no model API key** —
the runtime runs the model; the agent answers *as itself*.
the runtime runs the model; the agent answers _as itself_.

- **Hermes** — persistent daemon hosting multiple profiles (Victor, Mizu)
behind one gateway (`/v1/runs`). Owns the loop, streams, approvals,
Expand All @@ -74,14 +81,14 @@ the runtime runs the model; the agent answers *as itself*.

**Engine choice (per agent):**

| | **Runs** (recommended) | **Streaming** (Completions) |
|---|---|---|
| Loop owner | The runtime | Forge |
| Agent memory / persona / commands | **Preserved** — runs as itself | None (stateless) |
| Tools | The agent's own | Forge's chat allowlist + approvals |
| Same engine as dispatched work | Yes | No |
| Latency | Slightly higher | Lowest |
| Model | Provider-native | Any OpenAI-compatible |
| | **Runs** (recommended) | **Streaming** (Completions) |
| --------------------------------- | ------------------------------ | ---------------------------------- |
| Loop owner | The runtime | Forge |
| Agent memory / persona / commands | **Preserved** — runs as itself | None (stateless) |
| Tools | The agent's own | Forge's chat allowlist + approvals |
| Same engine as dispatched work | Yes | No |
| Latency | Slightly higher | Lowest |
| Model | Provider-native | Any OpenAI-compatible |

We default first-class agents to **Runs** so Hermes/Codex keep their memory,
session, and native commands. Flip to **Streaming** only for a stateless
Expand All @@ -96,7 +103,7 @@ functionality while the session is active**, but **ephemeral presence** — not
always online. Best for in-session, active work rather than always-on duty.

- **ACP** — Agent Client Protocol: a portable, bidirectional agent session.
The CLI chats *as itself* while live, with no per-vendor wiring.
The CLI chats _as itself_ while live, with no per-vendor wiring.
`transport: "acp"`, `chatMode: "acp"`. **Daemon-mediated** (ACP is stdio
JSON-RPC): on the daemon host set `FORGE_ACP_CMD="<agent> acp"` (e.g.
`claude-code-acp`, `codex acp`, `opencode acp`) and run `forge daemon start`
Expand All @@ -105,8 +112,8 @@ always online. Best for in-session, active work rather than always-on duty.
- **MCP (pull/act, today)** — the CLI connects over MCP with a Bearer key to
**read context and take actions**. It does **not** serve an interactive chat
turn (`chatMode: "none"`) — it has no model key and isn't a chat backend.
Chatting with such an agent shows a "no chat model configured" notice *by
design*; to chat with it as itself, give it an ACP session or promote it to
Chatting with such an agent shows a "no chat model configured" notice _by
design_; to chat with it as itself, give it an ACP session or promote it to
a first-class app-server runtime.

The `forge` **local daemon** is a managed bridge in this tier: `forge daemon
Expand All @@ -122,19 +129,19 @@ runtimes.

## At a glance

| Tier | Examples | Transport | Presence | Chat | Best for |
|------|----------|-----------|----------|------|----------|
| 1 — First-class | Hermes, Codex app server | `runs-api`, `app-server` | Always-on | Runs (or Streaming) | Full members: chat + dispatch + orchestration |
| 2 — Session CLI | Claude Code, Codex CLI, OpenCode | `acp`, `mcp`, `local-daemon` | Session/ephemeral | ACP (as itself) or pull/act | In-session active work |
| 3 — Basic | Custom bot | `webhook`, `http` | Delivery-derived | None | BYO integrations |
| Tier | Examples | Transport | Presence | Chat | Best for |
| --------------- | -------------------------------- | ---------------------------- | ----------------- | --------------------------- | --------------------------------------------- |
| 1 — First-class | Hermes, Codex app server | `runs-api`, `app-server` | Always-on | Runs (or Streaming) | Full members: chat + dispatch + orchestration |
| 2 — Session CLI | Claude Code, Codex CLI, OpenCode | `acp`, `mcp`, `local-daemon` | Session/ephemeral | ACP (as itself) or pull/act | In-session active work |
| 3 — Basic | Custom bot | `webhook`, `http` | Delivery-derived | None | BYO integrations |

## Codex sandboxing & approvals

A Codex app-server runtime touches a real filesystem, so its blast radius is
controlled on **two layers**:

1. **The bridge container is the hard boundary.** The reference bridge
(`~/docker/codex-bridge/`) mounts only the operator's Codex *auth*
(`~/docker/codex-bridge/`) mounts only the operator's Codex _auth_
(read-only) and a single scoped workspace (`/work`). The host filesystem is
unreachable from inside, so even a full-access Codex turn can't read host
secrets. This is fixed by the deployment, not a per-runtime setting. The
Expand All @@ -145,11 +152,11 @@ controlled on **two layers**:
`sandboxPolicy` / `approvalPolicy` / `cwd` overrides). Edit it in
**Settings → Runtimes → (the Codex runtime) → Codex sandbox**:

| Field | Values | Effect |
|-------|--------|--------|
| **Sandbox mode** | `Full access` · `Workspace-write` · `Read-only` | OS-level file/network scope. Workspace-write limits writes to the workspace root. |
| **Approval policy** | `Never` · `On request` · `On failure` · `Untrusted` | Anything but `Never` makes Codex raise an approval before risky commands/edits — Forge renders these as **accept/deny cards in chat**. |
| **Workspace root** | a path, e.g. `/work/agent-forge` | The turn's working dir; in workspace-write it's the only writable root. Setting it also makes Forge declare the Codex runtime as having repo tools for preflight and runtime cards. |
| Field | Values | Effect |
| ------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sandbox mode** | `Full access` · `Workspace-write` · `Read-only` | OS-level file/network scope. Workspace-write limits writes to the workspace root. |
| **Approval policy** | `Never` · `On request` · `On failure` · `Untrusted` | Anything but `Never` makes Codex raise an approval before risky commands/edits — Forge renders these as **accept/deny cards in chat**. |
| **Workspace root** | a path, e.g. `/work/agent-forge` | The turn's working dir; in workspace-write it's the only writable root. Setting it also makes Forge declare the Codex runtime as having repo tools for preflight and runtime cards. |

Defaults (no config) = **full access, no prompts** — the original behavior.
Forge tightens this per run: non-Execute dispatches (Research, Review, and
Expand Down
14 changes: 14 additions & 0 deletions docs/engineering/work-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ asks for a Delivery disposition instead of implying that work is still
executing. Resume it, attach or advance its delivery evidence, hand it off, or
abandon it explicitly.

Generic traffic from the same MCP connection is also connection evidence only.
One Desktop task or CLI process can own more than one Delivery session, so an
unrelated tool call must not refresh or clear every session's recovery request.
Only the exact session's heartbeat, PR attachment or advancement, handoff,
abandonment, terminal reconciliation, or audited operator confirmation changes
that session's recovery state.

MCP-quiet recovery is coordination evidence, not an independent product
decision. It does not by itself block a completion recommendation, and a safe
Ready to Close assessment supersedes the redundant recovery ask. Terminal
Expand Down Expand Up @@ -167,6 +174,13 @@ the configured In Review status. Research, review, and discussion runs do not
change issue status. These are server-side, audited transitions so clients do
not need to race a separate status mutation.

An MCP-owned Execute run cannot complete while its code-delivery session is
still Claimed, In Progress, or Stale without an implementation PR. Attach the native PR
with `workSessions.attachPullRequest`, or explicitly release a no-output session
with `workSessions.abandon`, before calling `runs.complete`. This prevents a
client ending its turn while Forge still reports an ambiguous active delivery
lease.

Release, deploy, and verification transitions require workspace admin authority.
Feature work may run in parallel, but merges and production delivery are
serialized.
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/mcp/rpc/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { mcpServerInfo } from "@/server/build-info";
import { FORGE_MCP_INSTRUCTIONS } from "@/server/services/mcp-instructions";
import { db } from "@/server/db";
import { touchAgentConnection, upsertAgentConnection } from "@/server/services/agent-connection";
import { resolveMcpQuietRequestsForConnection } from "@/server/services/work-session";
import { reconcileFreshMcpQuietRequestsForConnection } from "@/server/services/work-session";

/**
* Standard MCP (Model Context Protocol) endpoint — Streamable HTTP transport
Expand Down Expand Up @@ -250,7 +250,7 @@ async function resolveMcpConnection(
},
});
}
await resolveMcpQuietRequestsForConnection(db, auth.workspaceId, connection.id);
await reconcileFreshMcpQuietRequestsForConnection(db, auth.workspaceId, connection.id);
return connection;
}

Expand Down
Loading
Loading