Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
429515c
docs: design native OpenCode sessions
aksOps Jul 15, 2026
5b10739
docs: simplify OpenCode prompt handoff
aksOps Jul 16, 2026
b77a691
docs: plan native OpenCode integration
aksOps Jul 16, 2026
4024ad3
feat(adapter): support prepared command overrides
aksOps Jul 16, 2026
d155f32
feat(session): add secure runtime provider identity
aksOps Jul 16, 2026
fc6a9cf
feat(opencode): require supported provider command version
aksOps Jul 16, 2026
a11dffd
fix(opencode): reject prerelease versions
aksOps Jul 16, 2026
6fbb65f
feat(opencode): add authenticated server client
aksOps Jul 16, 2026
bd0780d
fix(opencode): harden client boundaries
aksOps Jul 16, 2026
233ca25
feat(opencode): supervise native server sessions
aksOps Jul 16, 2026
83e7ef0
fix(opencode): harden supervisor lifecycle
aksOps Jul 16, 2026
b659551
fix(opencode): preserve attach foreground tty
aksOps Jul 16, 2026
70b9cb0
fix(opencode): reap failed server attempts
aksOps Jul 16, 2026
ec6d4ba
refactor(opencode): remove generated identity plugin
aksOps Jul 16, 2026
b49466a
test(opencode): cover native session integration
aksOps Jul 16, 2026
87f6df3
fix(opencode): close native session regression
aksOps Jul 16, 2026
12f90e2
fix(opencode): close final session lifecycle gaps
aksOps Jul 16, 2026
232a0bb
fix(opencode): close native session review gaps
aksOps Jul 16, 2026
7af4628
fix(session): canonicalize host runtime directory
aksOps Jul 16, 2026
be1ce08
fix(session): preserve relative socket paths
aksOps Jul 16, 2026
7548988
test(opencode): verify expired attach cleanup
aksOps Jul 16, 2026
6cf7dd4
chore: ignore local superpowers artifacts
aksOps Jul 16, 2026
aa6185c
fix(session): resolve runtime path aliases
aksOps Jul 16, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ coverage.*
.vscode/
/.claude/worktrees/
/.worktrees/
/.superpowers/
/CLAUDE.md
/PLAN.md
graphify_out/
Expand Down
47 changes: 38 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Supported providers:

- Go 1.25+ to build from source (the pinned toolchain downloads automatically)
- Any provider CLI you want to manage already installed and authenticated
- OpenCode 1.18.1 or newer when using the OpenCode provider. If UAM reports an
older version, run `opencode upgrade 1.18.1` before dispatching or resuming.

That's it — agents are spawned directly under uam's own session hosts, so
there is nothing else to install.
Expand All @@ -48,8 +50,8 @@ from the dispatch UI instead of failing the whole app.

## Supported platforms

- Linux (Ubuntu), on amd64 and arm64
- macOS, on Intel and Apple silicon
- Linux (Ubuntu), on AMD64 and ARM64
- macOS, on AMD64 (Intel) and ARM64 (Apple silicon)
- Native Windows is not supported. Windows Terminal and PowerShell can be used
only as SSH clients connecting to a Linux or macOS host running `uam`.

Expand Down Expand Up @@ -205,9 +207,9 @@ Provider behavior:
heuristic.
- **Copilot**: exact resume — the session is named with the uam id at
dispatch (`--name`) and resumed by that exact name (`--resume=<id>`).
- **OpenCode**: UAM learns the current root conversation ID and resumes it
exactly with `--session`. Older records without an identity use guarded
`-c` latest-conversation behavior.
- **OpenCode**: exact resume only. UAM learns the current root conversation ID
and resumes it with `--session`. A stopped legacy record without a valid
exact identity cannot be resumed; dispatch a new Managed Session instead.
- **Oh My Pi**: new sessions receive a UAM-ID-specific provider state directory,
making `-c` exact. Legacy records without that directory retain guarded
latest-conversation behavior.
Expand All @@ -231,6 +233,32 @@ Managed Session*. It intentionally does not create another UAM row or host. UAM
tracks the newly selected root conversation for later exact resume. To get two
independently attachable sessions, use `uam new` or `uam dispatch` again.

Each managed OpenCode terminal owns a private authenticated server bound to a
distinct loopback port. UAM uses that server to create or validate the exact
root conversation, attach to that exact ID, and observe `/new` root changes.
Consequently, two OpenCode sessions in the same Workspace retain independent
ports, credentials, terminal hosts, and provider conversation IDs.

### OpenCode upgrade cleanup

Current UAM releases do not create, inspect, repair, execute, or delete the
legacy identity plugin at
`$XDG_STATE_HOME/uam/providers/opencode/uam-identity-plugin.mjs` (under
`~/.local/state` when `XDG_STATE_HOME` is unset). A stale file there is inert;
its contents, type, ownership, or permissions cannot block OpenCode launch.

No automatic cleanup is performed. If no older UAM installation still needs
that generated state, cleanup is optional. First inspect and verify the exact
UAM-generated directory, then remove only that directory:

```sh
legacy_dir="${XDG_STATE_HOME:-$HOME/.local/state}/uam/providers/opencode"
printf 'Review before removal: %s\n' "$legacy_dir"
ls -la -- "$legacy_dir"
# After verifying the printed path and contents:
rm -rf -- "$legacy_dir"
```

The terminology and compatibility decision are documented in
[Managed Session vs. Provider Conversation](docs/adr/0001-managed-session-vs-provider-conversation.md).

Expand Down Expand Up @@ -273,10 +301,11 @@ Use `uam dispatch --safe ...` when you want the provider's default approval
behavior instead. Safe mode changes provider arguments; it is not an operating-
system sandbox and does not reduce the permissions of the `uam` process itself.

OpenCode is capability-probed before launch. In default yolo mode, UAM adds
`--auto` only when the installed OpenCode advertises that flag; older versions
continue without an unsupported argument. This preserves compatibility but does
not turn OpenCode or UAM into a sandbox.
OpenCode keeps the same safety-mode contract as the other providers. Default
yolo mode automatically approves permission requests for the active root
conversation tree. `uam dispatch --safe ...` leaves OpenCode permission prompts
visible for the user and does not auto-reply. Safe mode still is not an
operating-system sandbox.

`uam` does not make git checkpoints, stash changes, or modify your repository on
its own. It starts and manages agent sessions; the provider remains responsible
Expand Down
98 changes: 98 additions & 0 deletions docs/research/2026-07-15-opencode-session-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# OpenCode session identity research

Date: 2026-07-15
Scope: OpenCode v1.18.1 and UAM's `uam-identity-plugin.mjs` bridge

## Conclusion

Keep the generated identity plugin for the current UAM architecture. OpenCode
v1.18.1 has reliable native primitives for resuming an **existing** exact
session, but its supported TUI CLI cannot create a new session with a
caller-selected ID or print the ID of the session it creates. The plugin remains
the smallest supported way for UAM to learn the active root session after TUI
startup and after `/new`.

There is a plugin-free design available through OpenCode's documented server
and SDK: start `opencode serve`, call `POST /session`, retain the returned ID,
then run `opencode attach <url> --session <id>`. That is not a drop-in change.
UAM would become responsible for a second process, local authentication, port
allocation, readiness, shutdown, and event correlation. `/new` would still
require watching the server event stream; a dedicated OpenCode server per UAM
managed session would make that correlation unambiguous.

## Upstream facts

- The latest non-draft, non-prerelease release at the time of research is
[v1.18.1](https://github.com/anomalyco/opencode/releases/tag/v1.18.1),
published 2026-07-14 at 21:37:54 UTC.
- The TUI accepts `--continue`/`-c` for the latest session and
`--session`/`-s` for an exact existing session. `--fork` creates a branch of
the selected session. These are documented in the
[CLI reference](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/web/src/content/docs/cli.mdx)
and defined by the
[TUI command](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/opencode/src/cli/cmd/tui.ts).
- `--session` is not a create-or-resume flag. The TUI validates the ID before
starting, and `opencode run --session` fetches the session and exits with
"Session not found" when it does not exist
([validation source](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/opencode/src/cli/tui/validate-session.ts),
[run source](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/opencode/src/cli/cmd/run.ts#L456-L533)).
- The supported `POST /session` API creates a session and returns the resulting
`Session`, including its generated ID. Its public request accepts fields such
as `parentID`, `title`, `agent`, `model`, metadata, permissions, and workspace,
but not a caller-provided session ID
([v1.18.1 OpenAPI](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/sdk/openapi.json#L5404-L5508),
[server docs](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/web/src/content/docs/server.mdx#L146-L167)).
- OpenCode officially supports `opencode serve`, an SDK client, an SSE event
stream at `GET /event`, and `opencode attach <url> --session <id>`
([server docs](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/web/src/content/docs/server.mdx),
[SDK docs](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/web/src/content/docs/sdk.mdx),
[attach source](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/opencode/src/cli/cmd/attach.ts)).
- The OpenAPI also contains a newer `/api/session` surface whose create body can
accept an `id`. It should not yet be the UAM compatibility contract: upstream's
own V2 session specification describes the V2 event family as experimental and
unshipped and lists remaining V1 parity work
([V2 specification](https://github.com/anomalyco/opencode/blob/v1.18.1/specs/v2/session.md),
[OpenAPI preview](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/sdk/openapi.json#L10110-L10188)).
- Plugins and session events are a documented public extension mechanism.
`session.created` and `session.updated` are listed events, and the published
plugin type exposes `Hooks.event({ event })`
([plugin docs](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/web/src/content/docs/plugins.mdx#L126-L190),
[plugin types](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/plugin/src/index.ts)).
The event schema includes both `properties.sessionID` and the full
`properties.info`, so UAM's current root-ID extraction shape remains valid in
v1.18.1
([event schema](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/sdk/openapi.json#L33885-L33935)).
No explicit long-term event ABI guarantee was found, so UAM should keep its
compatibility tests and version probing.

## Multiple sessions and `/new`

OpenCode stores multiple root sessions in one project and can resume each by
exact ID. The ambiguous operation is `--continue`, which deliberately selects
the most recent root session. Therefore multiple concurrent UAM sessions in one
workspace are safe only after each UAM record has learned and retained its own
OpenCode ID; the existing `-c` fallback for legacy/unidentified records can
resume the wrong conversation.

In v1.18.1, `/new` routes the TUI back to its home screen; the next submitted
prompt creates another root session
([TUI command source](https://github.com/anomalyco/opencode/blob/v1.18.1/packages/tui/src/app.tsx#L570-L594)).
UAM's plugin observes that new root `session.created` event and replaces the
identity handoff for that managed UAM session. Consequently, reattaching the UAM
session resumes the conversation created by `/new`; the previous OpenCode
conversation still exists but is no longer the one mapped to that UAM record.
This is expected from the current one-UAM-record-to-one-active-provider-session
model, not evidence that OpenCode lacks multiple sessions.

## Recommendation

1. Retain the plugin and add the guided permission bootstrap for UAM-owned
directories (`0700`) and the generated module (`0600`).
2. Add an OpenCode v1.18.1 compatibility test for `session.created` and
`session.updated`, including `/new` replacing only the owning UAM identity.
3. Keep `--session <learned-id>` as the normal resume path and surface a clear
warning whenever UAM must fall back to `-c` in a workspace with more than one
retained OpenCode session.
4. Re-evaluate a plugin-free server adapter only when UAM is prepared to own the
server lifecycle, or after OpenCode promotes caller-supplied session creation
to its documented stable API/CLI.
Loading
Loading