Skip to main content

oc-subagent

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.

Source facts

Repository
grahama1970/agent-stack-public
Last source activity
September 24, 2026 at 15:51
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
43 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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> ```
View on GitHub