Use OpenCode serve child sessions as persistent, inspectable subagents. Use when proving or operating OpenCode subagents, child-session reuse, parent/child session maps, concurrent subagent fan-out, DAG-like task probes, SSE/event capture, or persistence across multiple turns without the full scillm transport/orchestrator overhead.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add grahama1970/agent-skills --skill oc-subagent --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Oc Subagent?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/grahama1970-oc-subagent)More formats (shields.io, HTML) on the badges page.
---
name: oc-subagent
description: >
Use OpenCode serve child sessions as persistent, inspectable subagents. Use when
proving or operating OpenCode subagents, child-session reuse, parent/child
session maps, concurrent subagent fan-out, DAG-like task probes, SSE/event
capture, or persistence across multiple turns without the full scillm
transport/orchestrator overhead.
triggers:
- oc-subagent
- OpenCode subagent persistence
- persistent OpenCode child session
- child-session reuse
- concurrent OpenCode subagents
- prove subagents persist between tasks
- OpenCode parent child session map
- simple subagent DAG probe
provides:
- opencode-subagent-control
- child-session-persistence-proof
- concurrent-subagent-probe
composes:
- agentic-evals
taxonomy:
- validation
- orchestration
- persistence
- concurrency
disciplines:
- agentic-orchestration
---
# oc-subagent
## Overview
Use this skill when a project agent needs a simple, direct OpenCode serve MVP for subagents: one parent session, stable logical subagent ids, child sessions for executors, blocking turn completion, and proof artifacts. Prefer this skill over hand-written OpenCode endpoint logic.
This skill intentionally stays below the full `$scillm` transport/orchestrator layer. Use `$scillm` transport when the work needs durable DAG state, retries, validation gates, amendment, or UI steering. Use `$oc-subagent` when the immediate question is whether OpenCode child sessions can run concurrently and preserve state across turns.
## Core Invariants
1. Create exactly one parent OpenCode session for the test run unless explicitly recovering from failure.
2. Create one child session per logical subagent using `parentID`.
3. Track a stable logical id such as `subagent_1` separately from the physical `child_session_id`.
4. Reuse the same child session for repeated turns to the same logical subagent.
5. Serialize turns sent to the same `subagent_run_id`.
6. Dispatch independent `subagent_run_id`s concurrently only when they use different child sessions.
7. Never advance a dependent task based only on prompt dispatch or partial SSE output.
8. Advance only after the turn returns a terminal HTTP response or equivalent terminal run status and the result validates.
9. Persist session maps, request/response artifacts, raw SSE, normalized JSONL events, heartbeats, task status, child listings, message listings, and final proof JSON.
10. A persistence prompt must not leak the prior result or its ingredients (the turn-2 prompt uses no digits).
11. Prove session memory with a negative control: the same turn-2 prompt sent to a fresh child session that never ran turn 1 must not produce the persisted answer.
12. Derive proof booleans from recorded timestamps and prompt scans, never hard-code them.
## Session Model
```text
project_agent_run_id
└── parent_session_id
├── child_session_id for subagent_1
├── child_session_id for subagent_2
└── child_session_id for subagent_3
```
The project agent should reason in logical ids:
```text
subagent_1
subagent_2
subagent_3
```
The skill user maps those to physical OpenCode ids:
```json
{
"run_id": "oc-subagent-persistence-001",
"parent_session_id": "ses_parent",
"subagents": {
"subagent_1": {"child_session_id": "ses_child_a"},
"subagent_2": {"child_session_id": "ses_child_b"},
"subagent_3": {"child_session_id": "ses_child_c"}
}
}
```
## Minimal MVP Probe
Use this task list to prove persistence and concurrency without `$scillm` transport overhead:
```text
1. Project agent computes 2 + 2 = 4.
2. subagent_1 turn 1:
"Compute 3 * 6, then add the project-agent previous result, which is 4.
Store the resulting number as last_result."
Expected: 22
3. In parallel:
subagent_2: "What is the capital of France?"
Expected: Paris
subagent_3: "In simple everyday examples, what color is most commonly associated with an apple?"
Expected: red
4. Wait for both subagent_2 and subagent_3 to finish.
5. subagent_1 turn 2 (same child session, value not restated):
"Add ten to your last_result from this same session."
Expected: 32
6. Negative control (fresh child session, no turn 1):
Send the turn-2 prompt to a brand-new child session that never ran turn 1.
Expected: it must NOT produce 32 (it has no last_result to recall).
```
The turn-2 `subagent_1` prompt must not contain `22`, `18`, `4`, or `32`; it uses the word "ten" and contains no digits. If it does, the test proves prompt leakage, not session persistence. If the negative-control session produces `32` anyway, the `32` from the real run is not trustworthy evidence of session memory and the proof fails.
## Direct OpenCode Serve Calls
Prefer a local OpenCode serve instance:
```bash
export OPENCODE_BASE="${OPENCODE_BASE:-http://127.0.0.1:4096}"
export OC_AGENT="${OC_AGENT:-build}"
export OC_MODEL="${OC_MODEL:-}"
export RUN_ID="${RUN_ID:-oc-subagent-persistence-$(date +%Y%m%dT%H%M%S)}"
export ARTIFACT_DIR="${ARTIFACT_DIR:-./artifacts/$RUN_ID}"
mkdir -p "$ARTIFACT_DIR"
```
Use OpenCode agent profiles such as `build`, `plan`, `general`, or configured local profiles. Do not put chat model ids such as `opencode-go/kimi-k2.6` in the OpenCode `agent` field.
For this MVP proof, prefer a cheap model because the questions are simple and the proof target is child-session behavior. The live runner auto-selects a model unless `OC_MODEL` is set:
1. Local/free Ollama Qwen models, preferring `ollama/qwen3:14b` when present, then available local Qwen variants such as `ollama/qwen3:8b-nothink`, `ollama/qwen3:8b`, or `ollama/qwen2.5:14b`.
2. Cheap OpenCode Go fallback, currently `opencode-go/kimi-k2.6`.
3. Paid/high-confidence fallback, currently `openai/gpt-5.5`.
`OC_MODEL` is sent as the message `model` object; it is not the `agent` profile. Set `OC_MODEL_CANDIDATES` to a comma-separated list to override the auto-selection order. Set `OC_OLLAMA_MODEL` to prefer a specific local Ollama tag. The selected model and rejected candidates are written to `model-selection.json`.
Use different models for different work:
| Work type | Default model policy |
|-----------|----------------------|
| Cheap MVP persistence/concurrency proof | Local Ollama Qwen first, then OpenCode Go |
| Design critique, information architecture, broad synthesis | Moonshot AI Kimi K2.6 when configured, or OpenCode Go Kimi K2.6 |
| Coding, debugging, patch planning, hard correctness gates | GPT-5.5 high reasoning via OAuth/profile only when the task needs it |
Do not spend GPT-5.5 high reasoning on this MVP sanity unless cheaper candidates fail or the runner is diagnosing a real substrate bug.
## Live Sanity
Run the real MVP task list against a local OpenCode serve sidecar:
```bash
OPENCODE_BASE=http://127.0.0.1:4098 \
ARTIFACT_DIR=/tmp/oc-subagent/live-proof-$(date +%Y%m%dT%H%M%S) \
./sanity-live.sh
```
Expected success output:
```json
{
"verdict": "PASS",
"artifact_dir": "/tmp/oc-subagent/live-proof-..."
}
```
If OpenCode is unreachable, the script writes `proof.json` with `verdict: BLOCKED`. If a task answer, session id, concurrency, or prompt-leak invariant fails, it writes `verdict: FAIL`.
Minimal helpers:
```bash
oc_json_id() {
jq -r '.id // .ID // .sessionID // .session_id // empty'
}
oc_message_body() {
local text="$1"
jq -n --arg agent "$OC_AGENT" --arg text "$text" \
'{agent: $agent, parts: [{type: "text", text: $text}]}'
}
oc_create_parent() {
local title="$1"
curl -sS -X POST "$OPENCODE_BASE/session" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg title "$title" '{title: $title}')"
}
oc_create_child() {
local parent_id="$1"
local title="$2"
curl -sS -X POST "$OPENCODE_BASE/session" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg parentID "$parent_id" --arg title "$title" '{parentID: $parentID, title: $title}')"
}
oc_send_turn_and_wait() {
local child_session_id="$1"
local text="$2"
curl -sS -X POST "$OPENCODE_BASE/session/$child_session_id/message" \
-H 'Content-Type: application/json' \
-d "$(oc_message_body "$text")"
}
```
## Evidence Contract
Write artifacts under one run directory:
```text
artifacts/<run_id>/
project-agent-state.json
parent-session.json
session-map.json
events.sse
subagent_1-session.json
subagent_1-turn-1-request.txt
subagent_1-turn-1-response.json
subagent_1-turn-2-request.txt
subagent_1-turn-2-response.json
subagent_2-session.json
subagent_2-concurrent-request.txt
subagent_2-concurrent-response.json
subagent_3-session.json
subagent_3-concurrent-request.txt
subagent_3-concurrent-response.json
subagent_1_negative-session.json
subagent_1_negative-request.txt
subagent_1_negative-response.json
children-after.json
messages-subagent_1-after.json
model-selection.json
events.jsonl
heartbeats.jsonl
timeline.jsonl
task-status.json
proof.json
```
Final proof shape:
```json
{
"verdict": "PASS",
"run_id": "oc-subagent-persistence-001",
"parent_session_id": "ses_parent",
"model_selection": {
"selected_model": "ollama/qwen3:8b-nothink",
"artifact": "model-selection.json"
},
"project_agent_persistence": {
"step_1_result": 4,
"state_survived_unrelated_tasks": true
},
"sequential_subagent_persistence": {
"subagent_run_id": "subagent_1",
"child_session_id_turn_1": "ses_child_a",
"child_session_id_turn_2": "ses_child_a",
"same_child_session": true,
"turn_1_answer": 22,
"turn_2_answer": 32,
"turn_2_prompt_did_not_leak_prior_value": true,
"turn_2_waited_for_turn_1": true,
"passed": true
},
"concurrent_subagents": {
"subagent_2_child_session_id": "ses_child_b",
"subagent_3_child_session_id": "ses_child_c",
"subagent_2_answer": "Paris",
"subagent_3_answer": "red",
"both_dispatched_before_first_result_consumed": true,
"passed": true
},
"negative_control": {
"subagent_run_id": "subagent_1_negative",
"child_session_id": "ses_child_neg",
"prompt_is_turn_2_without_turn_1": true,
"answer": null,
"produced_32": false,
"passed": true
},
"failure_reasons": [],
"artifacts": {
"model_selection": "model-selection.json",
"session_map": "session-map.json",
"events": "events.sse",
"events_jsonl": "events.jsonl",
"heartbeats": "heartbeats.jsonl",
"timeline": "timeline.jsonl",
"task_status": "task-status.json",
"children_after": "children-after.json",
"messages_subagent_1_after": "messages-subagent_1-after.json",
"negative_control_session": "subagent_1_negative-session.json",
"negative_control_response": "subagent_1_negative-response.json"
}
}
```
## Observability Contract
The live runner follows the scillm transport convention: raw SSE is preserved for forensics, normalized JSONL is written for machines, and task state is snapshotted for operators.
```text
events.sse raw OpenCode event stream
events.jsonl normalized SSE rows keyed by session/subagent when known
timeline.jsonl project-agent lifecycle rows such as run_started, task_started, task_completed
heartbeats.jsonl periodic liveness rows for active DAG nodes/subagent calls
task-status.json latest per-node state snapshot
```
Completion still gates on a terminal message response plus result validation. Heartbeats and streamed deltas prove liveness and diagnosability; they are not sufficient by themselves to advance a dependent DAG node.
## Failure Conditions
Fail the proof if any of these occur:
- `subagent_1` turn 2 uses a different child session without an explicit fork/recovery receipt.
- Turn 2 dispatches before turn 1 returned and validated.
- Two simultaneous turns are sent to the same child session.
- Concurrent distractor results are consumed before both independent calls were dispatched.
- The turn-2 `subagent_1` prompt includes `22`, `18`, `4`, or `32`.
- The negative-control session (turn-2 prompt, no turn 1) produces `32`, which would mean `32` is not proof of session memory.
- A measured invariant (`turn_2_prompt_did_not_leak_prior_value`, `turn_2_waited_for_turn_1`, `both_dispatched_before_first_result_consumed`) is false rather than asserted true.
- The proof is based only on natural-language claims rather than saved artifacts.
- A chat model id is used as the OpenCode `agent` field.
- `events.jsonl`, `heartbeats.jsonl`, `timeline.jsonl`, or `task-status.json` is missing from a live run.
## Output Summary
Return a concise final summary:
```text
VERDICT: PASS | FAIL
Project-agent state: PASS | FAIL
Sequential subagent persistence: PASS | FAIL
Concurrent subagents: PASS | FAIL
Final values:
project_agent_step_1 = 4
subagent_1_final = 32
subagent_2 = Paris
subagent_3 = red
Artifact directory: artifacts/<run_id>
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!