# Delegating to OpenCode — Worked Patterns

Patterns observed while delegating a 4-file Python refactor (`QuotaHub` data-source addition) to OpenCode via the `myrelay/GLM-5.1` → `myrelay/GLM-5.2` chain on 2026-07-05.

## Session Setup

- **Project:** `/root/QuotaHub` (Python 3.13 / FastAPI / httpx)
- **Task:** add `MiniMax` data source (new endpoint + new config block + new sample in `config.json.example`), cloning logic from existing `ollama_quota.py` / `quota.py` dataclass pattern. The existing module already had `MiniMax_quota.py` from a previous, partial agent run that I never executed — so I was about to write prompts over a partially-written baseline.
- **Model:** `myrelay/GLM-5.1` (1M context) for the first attempt, swapped to `myrelay/GLM-5.2` after the user requested the change.
- **Prompt size:** 89 lines (initial) / 53 lines (resume).

## Pitfalls Hit (and how to avoid them)

### 1. First attempt: `opencode run ... | tail -300` ate all output

```bash
terminal(
  background=true, notify_on_complete=true, timeout=1800,
  command="... opencode run -m myrelay/GLM-5.1 \"$PROMPT\" 2>&1 | tail -300"
)
```

Background started fine. `tail -300` then **buffered everything** until process exit. So while the agent was actively working, stdout showed empty. Consequence: I killed the process at ~2 minutes thinking it was hung (status was running, uptime 96s, output_preview "", total_lines 0).

**Fix:** Always use `tee` to a log file:

```bash
terminal(
  background=true, notify_on_complete=true, timeout=1800,
  command="opencode run -m myrelay/GLM-5.2 --dir /root/QuotaHub --format default \
    \"$PROMPT\" 2>&1 | tee /tmp/opencode_glm52.log | tail -400"
)
```

This both preserves the full transcript for replay AND lets `tail -50 /tmp/opencode_glm52.log` show progress at any moment.

### 2. The "model switch" caused a partial-state disaster

User said "opencode 用 GLM-5.2" mid-run. I killed GLM-5.1 at the 90-second mark thinking it was stuck. Reality (discovered later by checking mtime):

```
$ ls -la /root/QuotaHub/backend/app/
-rw-r--r--  1 root root 8886 Jul  5 02:51 config.py   # original
-rw-r--r--  1 root root  5642 Jul  5 04:53 MiniMax_quota.py   # GLM-5.1 wrote this AT 04:53
-rw-r--r--  1 root root  9127 Jul  5 04:53 config.py   # GLM-5.1 ALSO rewrote config.py at 04:53
-rw-r--r--  1 root root 10951 Jul  5 02:51 ollama_quota.py
-rw-r--r--  1 root root 10713 Jul  5 02:51 quota.py
-rw-r--r--  1 root root  6341 Jul  5 02:51 usage.py
```

GLM-5.1 had actually written **both** `MiniMax_quota.py` AND a partial `config.py` (with `MiniMaxAccountConfig` dataclass) — about 70% of the work. Killing it was wrong. Worse, the GLM-5.2 run I started next had no idea any of this existed.

**Lesson:** Always inspect mtime before killing any `opencode run`. Specifically:

```bash
find /root/QuotaHub -type f -newer /tmp/opencode_start_marker 2>/dev/null
```

If files exist that weren't there at the start, the agent did work you can't see — investigate before kill.

### 3. Resume prompt had to enumerate everything that was already done

The new GLM-5.2 prompt I wrote said:

```
## Context: partial work ALREADY done
- ✅ `/root/QuotaHub/backend/app/MiniMax_quota.py` — FULLY IMPLEMENTED (~178 lines).
  Don't rewrite. Just keep it.
- ✅ `/root/QuotaHub/backend/app/config.py` — partial:
  - Added `MiniMaxAccountConfig` dataclass (name, api_key, show_session, show_weekly)
  - Added `MiniMax_accounts: list[MiniMaxAccountConfig]` to `AppConfig` dataclass
  - Added `_parse_minimax_accounts` helper function (NOT YET CALLED)
  - **NOT YET DONE in config.py:**
    - `parse_config` still uses `raw.get(...)` and parses inline differently —
      Make `parse_config` call `_parse_minimax_accounts(minimax_accounts_raw)`.
    - `AppConfig()` constructor at bottom of `parse_config` is MISSING the
      `MiniMax_accounts=` argument — add it.
    ...
- ❌ `/root/QuotaHub/backend/app/main.py` — NOT YET TOUCHED. ...
- ❌ `/root/QuotaHub/config.json.example` — NOT YET TOUCHED. ...
```

Without this enumeration, GLM-5.2 would have rewritten `MiniMax_quota.py` from scratch (regression) and re-added config work the model already had.

### 4. Background runs need an absolute timeout

```bash
terminal(background=true, notify_on_complete=true, timeout=1800, command=...)
```

The default `timeout=180` (3 min) is too short for multi-file refactors. GLM-5.2 finished in ~5 minutes wall time (writing ~70 lines spread across 3 files, then validating). Setting `timeout=1800` (30 min) is safe — Hermes will keep the handle alive and notify when it exits.

## Verification Once the Agent Says "Done"

Even after the clean "Done." line from GLM-5.2, **verify yourself**:

```bash
$ python -m py_compile backend/app/MiniMax_quota.py backend/app/config.py backend/app/main.py
PY_COMPILE_OK exit=0

$ cd backend && python -c "
from app.config import parse_config, mask_minimax_key
raw = {'MiniMax_accounts': [{'name':'A','api_key':'sk-1234567890abcdef'}]}
cfg = parse_config(raw)
assert cfg.MiniMax_accounts[0].name == 'A'
assert mask_minimax_key('sk-1234567890abcdef') == 'sk-1…cdef'
print('WIRING_OK')
"
WIRING_OK

$ git diff --stat
 backend/app/config.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++--
 backend/app/main.py   | 28 ++++++++++++++++++++-
 config.json.example   |  3 ++-
```

The smoke-import catches wiring mistakes py_compile can't see. **Always run both.**

## Final Outcome

| File | Δ | Status |
|---|---|---|
| `backend/app/MiniMax_quota.py` | +178 (new) | ✅ written by GLM-5.1, retained by GLM-5.2 |
| `backend/app/config.py` | +64/-4 | ✅ GLM-5.1 partial + GLM-5.2 finished wiring |
| `backend/app/main.py` | +27/-1 | ✅ added by GLM-5.2 |
| `config.json.example` | +2/-1 | ✅ added by GLM-5.2 |
| Total | +271/-6 | py_compile OK, smoke OK, no default API key |

The `MiniMax_quota.py` import (`from .config import MiniMaxAccountConfig`) referenced the dataclass GLM-5.1 had added to config.py. So the resume prompt had to confirm **both** pieces existed at the right paths with the right names before letting GLM-5.2 touch anything.

## Template: opencode Run Wrapper

```bash
LOGFILE="${1:-/tmp/opencode.log}"
PROMPT_FILE="${2:-/tmp/opencode_prompt.md}"
MODEL="${3:-myrelay/GLM-5.2}"
DIR="${4:-$PWD}"

terminal(
  background=true,
  notify_on_complete=true,
  timeout=1800,
  command="PROMPT=\$(cat $PROMPT_FILE); opencode run -m $MODEL --dir $DIR \
    --format default \"\$PROMPT\" 2>&1 | tee $LOGFILE | tail -400"
)
```

Then to follow up mid-task or post-task:

```bash
tail -50 "$LOGFILE"             # live progress
find "$DIR" -type f -newer /tmp/baseline_marker | head -30  # files written
git -C "$DIR" diff --stat       # changes vs last commit
```

## Reusable Tips Summary

1. Use `tee $LOGFILE | tail -N`, never `tail -N` alone, for `opencode run`.
2. Set `timeout=1800` for multi-file work; the default 180s is for one-shots.
3. Wait at least 90-120 seconds before suspecting a hang — long rewrites take time.
4. Before killing any `opencode run`, run `find $DIR -newer <marker>` to see if the agent already wrote files.
5. For mid-task model swap or restart, enumerate state in a RESUME prompt: which files exist, what's in them, what remains.
6. After the agent's "Done." line, ALWAYS verify with `py_compile` + smoke import + `git diff --stat`. The agent's claim is not the fact.
