Skip to content

Commit 1493a8b

Browse files
committed
docs(openspec): add harden-opencode-runtime-integration design.md
1 parent d90e7cb commit 1493a8b

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

  • openspec/changes/harden-opencode-runtime-integration
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
## Context
2+
3+
The SDK currently has three independent integration channels:
4+
5+
1. HTTP REST through `OpenCodeHttpClient`.
6+
2. SSE through `OpenCodeSseClient`.
7+
3. Local CLI through `OpenCodeCli` and `OpenCodeCliExecutor`.
8+
9+
The public surface is broad, but all CLI commands ultimately use one synchronous capture executor. This is suitable for short commands but not for event streaming, interactive sessions, or long-running servers. The HTTP chat facade also treats more than one event shape as append-only text, while upstream exposes explicit delta events.
10+
11+
## Goals
12+
13+
- Make the modeled Java API match upstream OpenCode command and event contracts.
14+
- Provide safe process execution for short-lived automation.
15+
- Provide streaming process execution without waiting for exit.
16+
- Provide lifecycle-managed long-running OpenCode processes.
17+
- Preserve independent HTTP, SSE, and CLI channels.
18+
- Keep old branch compatibility constraints explicit rather than hiding them behind one implementation.
19+
20+
## Non-Goals
21+
22+
- Reimplement the OpenCode Web UI in Java.
23+
- Make ACP a full protocol client in this change; this change provides a correct managed launcher boundary. A full ACP protocol client can be a separate capability.
24+
- Automatically abort a server-side session whenever a local SSE subscriber disconnects.
25+
- Default-enable `--auto` or any permission-bypassing option.
26+
- Promise multi-tenant security solely from per-process config or environment isolation.
27+
28+
## Decisions
29+
30+
### 1. Separate three CLI execution modes
31+
32+
```text
33+
OpenCodeCli
34+
|
35+
+-- execute(...) short command, bounded capture
36+
|
37+
+-- stream(...) incremental stdout/stderr + exit future
38+
|
39+
+-- startManaged(...) long-running process handle
40+
```
41+
42+
The existing synchronous `execute` path remains the default for short commands.
43+
44+
### 2. Concurrency belongs to the executor instance
45+
46+
`maxConcurrentExecutions > 0` is enforced by a semaphore owned by `OpenCodeCliExecutor`. A permit is acquired before spawn and released after process termination or spawn failure.
47+
48+
This prevents configuration that appears to limit concurrency while having no runtime effect.
49+
50+
### 3. Output capture is bounded independently from debug-log truncation
51+
52+
Debug log truncation only protects log messages; it does not bound process capture memory.
53+
54+
Add explicit capture limits:
55+
56+
- `maxStdoutBytes`
57+
- `maxStderrBytes`
58+
59+
When the limit is exceeded, the result MUST mark the stream as truncated. Streaming consumers still receive data according to their configured mode, while retained tail/head policy is deterministic.
60+
61+
### 4. Environment configuration is per executor invocation
62+
63+
Add an execution context with:
64+
65+
- inherit parent environment: boolean
66+
- environment overrides: map
67+
- working directory override
68+
- optional stdin mode
69+
70+
Sensitive values MUST NOT be copied into debug output.
71+
72+
### 5. Long-running processes have no ordinary command timeout
73+
74+
A managed process uses separate time concepts:
75+
76+
- startup timeout: time to become ready
77+
- graceful shutdown timeout: time allowed after stop request
78+
- lifetime: unbounded unless the caller explicitly sets one
79+
80+
`OpenCodeServerHandle` exposes process state, actual address when known, exit future, stop/kill operations, and bounded log tail.
81+
82+
### 6. Server readiness is protocol-based when possible
83+
84+
For `serve` and `web`, readiness SHOULD be established via HTTP health probing against the resolved address instead of assuming that process spawn means service readiness.
85+
86+
For random ports, the launcher MUST obtain the actual bound address from a stable OpenCode output contract or a supplied fixed port. If no reliable random-port discovery is available, the managed API MUST require an explicit port rather than parse arbitrary human-oriented text heuristically.
87+
88+
### 7. CLI option arity is modeled exactly
89+
90+
`--share` is a boolean switch.
91+
92+
`--auto` is a boolean switch and defaults to false.
93+
94+
Interactive mode is modeled separately from the ordinary non-interactive result API; adding `--interactive` to an options object alone does not imply full PTY support.
95+
96+
### 8. SSE chat consumes deltas, snapshots update state
97+
98+
Text accumulation is driven by delta events such as `message.part.delta` and the version-specific canonical text-delta event.
99+
100+
`message.part.updated` is treated as a snapshot/state update and MUST NOT be blindly appended to accumulated text.
101+
102+
### 9. SSE subscription readiness is explicit
103+
104+
A subscription exposes:
105+
106+
- connected/readiness future
107+
- terminal failure future or callback
108+
- cancellation
109+
110+
The chat flow becomes:
111+
112+
```text
113+
ensure session
114+
-> create subscription
115+
-> await SSE connected
116+
-> submit prompt_async
117+
-> consume deltas
118+
-> idle => complete
119+
-> session error / transport error => fail
120+
```
121+
122+
### 10. Client-side unsubscribe and server-side abort remain separate
123+
124+
Cancelling a local stream MUST close its local subscription and timers.
125+
126+
Aborting the OpenCode session is an explicit operation because another client may still be observing or relying on that server-side execution.
127+
128+
A future convenience API may opt into “cancel and abort”, but it must be named explicitly.
129+
130+
### 11. Configuration model preserves unknown fields
131+
132+
The typed configuration model gains a `server` field.
133+
134+
Unknown properties MUST be captured using Jackson's any-setter / any-getter mechanism appropriate to each branch's Jackson major version. This allows read-modify-write flows without silently discarding newly introduced upstream configuration fields.
135+
136+
## Compatibility Strategy
137+
138+
### feature/3.0.x
139+
140+
- Java 21
141+
- Jackson 3 `tools.jackson.*`
142+
- primary implementation and integration tests
143+
144+
### feature/2.0.x
145+
146+
- Java 17
147+
- Jackson 2
148+
- same public behavior, compatibility-specific imports/APIs
149+
150+
### feature/1.0.x
151+
152+
The branch metadata currently conflicts: CI describes a JDK 8 line while its POM compiler release is 17. Before backporting, the branch baseline MUST be corrected or explicitly redefined. Runtime hardening MUST NOT be declared complete on this branch until its build baseline is resolved and verified.
153+
154+
## Failure Model
155+
156+
Short CLI execution MUST distinguish at least:
157+
158+
- success
159+
- non-zero exit
160+
- spawn failure
161+
- timeout
162+
- cancellation
163+
164+
Streaming/managed execution MUST surface terminal process failure separately from individual stderr lines.
165+
166+
SSE streaming MUST distinguish:
167+
168+
- OpenCode session error event
169+
- transport failure
170+
- JSON/event decoding failure according to configured strictness
171+
- local cancellation
172+
- completion by idle event
173+
174+
## Security
175+
176+
- `--auto` MUST default to false.
177+
- passwords and environment secrets MUST never appear in SDK debug output.
178+
- exposing Web/Serve on non-loopback interfaces SHOULD require explicit caller configuration.
179+
- CORS configuration and authentication are independent concerns and MUST NOT be conflated.
180+
181+
## Open Questions
182+
183+
No unresolved question blocks P0 implementation.
184+
185+
A full ACP Java protocol client is intentionally deferred to a separate proposal.

0 commit comments

Comments
 (0)