tandem-cli 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.claude/settings.local.json +6 -1
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.gitignore +3 -0
- tandem_cli-0.4.0/PKG-INFO +145 -0
- tandem_cli-0.4.0/README.md +119 -0
- tandem_cli-0.4.0/docs/configuration.md +114 -0
- tandem_cli-0.4.0/docs/development.md +98 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/formats.md +40 -4
- tandem_cli-0.4.0/docs/how-it-works.md +161 -0
- tandem_cli-0.4.0/docs/plans/2026-08-09-meta-harness-frame.md +1860 -0
- tandem_cli-0.4.0/docs/plans/2026-08-09-readme-restructure.md +367 -0
- tandem_cli-0.4.0/docs/plans/2026-08-10-idle-flip-status-probe.md +595 -0
- tandem_cli-0.4.0/docs/plans/2026-08-10-retire-interstitial-prompt.md +857 -0
- tandem_cli-0.4.0/docs/plans/2026-08-13-pr1-nharness-core.md +1813 -0
- tandem_cli-0.4.0/docs/plans/2026-08-13-pr2-opencode-adapter.md +1907 -0
- tandem_cli-0.4.0/docs/specs/2026-08-09-meta-harness-frame-design.md +186 -0
- tandem_cli-0.4.0/docs/specs/2026-08-09-model-pin-recovery-design.md +140 -0
- tandem_cli-0.4.0/docs/specs/2026-08-09-readme-restructure-design.md +117 -0
- tandem_cli-0.4.0/docs/specs/2026-08-10-remove-interstitial-prompt-design.md +102 -0
- tandem_cli-0.4.0/docs/specs/2026-08-13-opencode-harness-design.md +442 -0
- tandem_cli-0.4.0/docs/subagents.md +139 -0
- tandem_cli-0.4.0/docs/superpowers/plans/2026-08-11-process-warmup-live-validation.md +97 -0
- tandem_cli-0.4.0/docs/superpowers/plans/2026-08-11-process-warmup.md +1706 -0
- tandem_cli-0.4.0/docs/superpowers/plans/2026-08-12-process-warmup-pipelined.md +286 -0
- tandem_cli-0.4.0/docs/superpowers/specs/2026-08-10-idle-flip-status-probe.md +101 -0
- tandem_cli-0.4.0/docs/superpowers/specs/2026-08-11-process-warmup-design.md +221 -0
- tandem_cli-0.4.0/docs/superpowers/specs/2026-08-12-process-warmup-pipelined-design.md +125 -0
- tandem_cli-0.4.0/docs/why.md +89 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/.claude-plugin/plugin.json +1 -1
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/README.md +31 -12
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/agents/codex-worker.md +8 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/agents/gpt.md +8 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/pyproject.toml +1 -1
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/cli.py +254 -119
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/compat.py +7 -2
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/config.py +66 -1
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/constants.py +1 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/converter.py +5 -8
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/doctor.py +53 -129
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/events.py +30 -15
- tandem_cli-0.4.0/src/tandem/flip.py +325 -0
- tandem_cli-0.4.0/src/tandem/frame.py +287 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/harness/__init__.py +2 -4
- tandem_cli-0.4.0/src/tandem/harness/base.py +267 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/harness/claude_code.py +176 -12
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/harness/codex.py +83 -1
- tandem_cli-0.4.0/src/tandem/harness/opencode.py +772 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/hookroute.py +36 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/memory_sync.py +2 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/ops.py +131 -70
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/paths.py +8 -0
- tandem_cli-0.4.0/src/tandem/pinstash.py +97 -0
- tandem_cli-0.4.0/src/tandem/ptyrun.py +446 -0
- tandem_cli-0.4.0/src/tandem/runner.py +847 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/state.py +93 -59
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/sync.py +27 -54
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/tailer.py +12 -3
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/toolmap.py +8 -3
- tandem_cli-0.4.0/src/tandem/warm.py +172 -0
- tandem_cli-0.4.0/tests/conftest.py +280 -0
- tandem_cli-0.4.0/tests/test_cli.py +347 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_config.py +73 -1
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_converter.py +11 -5
- tandem_cli-0.4.0/tests/test_flip.py +586 -0
- tandem_cli-0.4.0/tests/test_frame.py +588 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_hookroute.py +65 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_memory_doctor.py +37 -1
- tandem_cli-0.4.0/tests/test_opencode.py +529 -0
- tandem_cli-0.4.0/tests/test_opencode_oracle.py +100 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_ops.py +122 -12
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_parse.py +41 -2
- tandem_cli-0.4.0/tests/test_participants.py +135 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_paths_compat.py +16 -0
- tandem_cli-0.4.0/tests/test_pinstash.py +94 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_plugin.py +8 -1
- tandem_cli-0.4.0/tests/test_ptyrun.py +582 -0
- tandem_cli-0.4.0/tests/test_runner.py +1547 -0
- tandem_cli-0.4.0/tests/test_state.py +234 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_sub.py +120 -28
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_sync.py +25 -3
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_tail.py +38 -6
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_toolmap.py +27 -10
- tandem_cli-0.4.0/tests/test_usage.py +274 -0
- tandem_cli-0.4.0/tests/test_warm.py +223 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/uv.lock +1 -1
- tandem_cli-0.2.0/.superpowers/sdd/.gitignore +0 -1
- tandem_cli-0.2.0/PKG-INFO +0 -389
- tandem_cli-0.2.0/README.md +0 -363
- tandem_cli-0.2.0/docs/gpt-subagent.gif +0 -0
- tandem_cli-0.2.0/src/tandem/harness/base.py +0 -87
- tandem_cli-0.2.0/src/tandem/ptyrun.py +0 -99
- tandem_cli-0.2.0/src/tandem/runner.py +0 -261
- tandem_cli-0.2.0/src/tandem/shell.py +0 -207
- tandem_cli-0.2.0/tests/conftest.py +0 -142
- tandem_cli-0.2.0/tests/test_cli.py +0 -205
- tandem_cli-0.2.0/tests/test_ptyrun.py +0 -14
- tandem_cli-0.2.0/tests/test_runner.py +0 -104
- tandem_cli-0.2.0/tests/test_shell.py +0 -402
- tandem_cli-0.2.0/tests/test_state.py +0 -147
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.claude-plugin/marketplace.json +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.github/workflows/ci.yml +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/LICENSE +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-07-29-native-tool-call-translation.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-07-31-codex-subagents.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-01-plugin-marketplace-install.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-01-sandbox-consent-and-manual-routing.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-02-plugin-auto-install.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-03-manual-default-model-passthrough.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-06-harness-startup-args.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-07-29-native-tool-call-translation-design.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-07-31-codex-subagents-design.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-08-02-plugin-auto-install-design.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-08-03-manual-default-model-passthrough-design.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-08-06-harness-startup-args-design.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/superpowers/plans/2026-07-29-tandem-shell.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/superpowers/specs/2026-07-29-tandem-shell-design.md +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/hooks/hooks.json +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/__init__.py +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/modelcat.py +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/plugin_setup.py +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/summarize.py +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/util.py +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/golden/claude-probe.jsonl +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/golden/codex-probe.jsonl +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_modelcat.py +0 -0
- {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_plugin_setup.py +0 -0
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
"Bash(curl -sL --max-time 30 -o bl_books_full.csv \"https://raw.githubusercontent.com/realpython/python-data-cleaning/master/Datasets/BL-Flickr-Images-Book.csv\")",
|
|
8
8
|
"Bash(curl -sL --max-time 30 -o chipotle.tsv \"https://raw.githubusercontent.com/justmarkham/DAT8/master/data/chipotle.tsv\")",
|
|
9
9
|
"Bash(git reset *)",
|
|
10
|
-
"Bash(/Users/bhavya/.claude/plugins/cache/claude-plugins-official/superpowers/6.2.0/skills/subagent-driven-development/scripts/task-brief *)"
|
|
10
|
+
"Bash(/Users/bhavya/.claude/plugins/cache/claude-plugins-official/superpowers/6.2.0/skills/subagent-driven-development/scripts/task-brief *)",
|
|
11
|
+
"WebFetch(domain:subreddittraffic.live)",
|
|
12
|
+
"Bash(grep -n *)",
|
|
13
|
+
"Read(//Users/bhavya/.tandem/**)",
|
|
14
|
+
"Read(//Users/bhavya/.config/**)",
|
|
15
|
+
"Bash(uv run *)"
|
|
11
16
|
]
|
|
12
17
|
}
|
|
13
18
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tandem-cli
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Meta-harness that runs Claude Code and Codex CLI with live trace sync.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Bhavya6187/tandem
|
|
6
|
+
Project-URL: Repository, https://github.com/Bhavya6187/tandem
|
|
7
|
+
Project-URL: Issues, https://github.com/Bhavya6187/tandem/issues
|
|
8
|
+
Author-email: Bhavya Agarwal <bhavya.6187@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,claude-code,cli,codex,session-sync
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: click>=8.1
|
|
22
|
+
Requires-Dist: pexpect>=4.9
|
|
23
|
+
Requires-Dist: pydantic>=2.7
|
|
24
|
+
Requires-Dist: watchdog>=4.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
<div align="center">
|
|
28
|
+
|
|
29
|
+
# 🤝 tandem
|
|
30
|
+
|
|
31
|
+
**One coding session. Multiple AI agents. Shared context.**
|
|
32
|
+
|
|
33
|
+
Use [Claude Code](https://docs.anthropic.com/en/docs/claude-code),
|
|
34
|
+
[OpenAI Codex CLI](https://github.com/openai/codex), and
|
|
35
|
+
[opencode](https://opencode.ai) in one continuous coding session. Work in
|
|
36
|
+
their native interfaces, press **Ctrl-]** to move to the next CLI, and
|
|
37
|
+
continue without copying prompts or rebuilding context by hand.
|
|
38
|
+
|
|
39
|
+
[](https://github.com/Bhavya6187/tandem/actions/workflows/ci.yml)
|
|
40
|
+
[](https://pypi.org/project/tandem-cli/)
|
|
41
|
+
[](https://github.com/Bhavya6187/tandem/blob/main/pyproject.toml)
|
|
42
|
+
[](https://github.com/Bhavya6187/tandem/blob/main/LICENSE)
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
You need Python 3.11+ and at least two supported CLIs installed and signed
|
|
51
|
+
in: [Claude Code](https://docs.anthropic.com/en/docs/claude-code),
|
|
52
|
+
[Codex CLI](https://github.com/openai/codex), or
|
|
53
|
+
[opencode](https://opencode.ai).
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv tool install tandem-cli # or: pip install tandem-cli
|
|
57
|
+
cd your-project
|
|
58
|
+
tandem
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Work normally in the CLI that opens. Press **Ctrl-]** to continue the same
|
|
62
|
+
session in the next CLI. If you press it while the model is working, tandem
|
|
63
|
+
waits until the response finishes; press it again to cancel the switch.
|
|
64
|
+
|
|
65
|
+
Exit normally when you're done. Come back later with:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
tandem resume # resume the latest session in this directory
|
|
69
|
+
tandem sessions # find recent sessions across directories
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
By default, tandem opens the first usable CLI in your configured order. Use
|
|
73
|
+
`tandem --active codex` or `tandem --active opencode` to choose another.
|
|
74
|
+
|
|
75
|
+
## Why tandem?
|
|
76
|
+
|
|
77
|
+
- **Keep going when a model hits its limit.** Move to another CLI and
|
|
78
|
+
continue with the same files and conversation history.
|
|
79
|
+
- **Bring different models to the same problem.** Ask another CLI for a
|
|
80
|
+
second opinion without copying a wall of context between terminals.
|
|
81
|
+
- **Keep the native tools you already use.** Claude Code, Codex, and
|
|
82
|
+
opencode retain their own interfaces, commands, keybindings, and MCP
|
|
83
|
+
servers.
|
|
84
|
+
- **Work through a single-provider outage.** If another configured CLI is
|
|
85
|
+
available, switch and continue until the affected provider recovers.
|
|
86
|
+
|
|
87
|
+
Want GPT subagents inside Claude Code too? See the
|
|
88
|
+
[GPT subagents guide](https://github.com/Bhavya6187/tandem/blob/main/docs/subagents.md).
|
|
89
|
+
For more use cases, see
|
|
90
|
+
[Why tandem?](https://github.com/Bhavya6187/tandem/blob/main/docs/why.md).
|
|
91
|
+
|
|
92
|
+
## How it works
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
┌──────────────────────────────────────────────┐
|
|
96
|
+
│ Work normally in a native coding CLI │
|
|
97
|
+
└──────────────────────┬───────────────────────┘
|
|
98
|
+
▼
|
|
99
|
+
┌──────────────────────────────────────────────┐
|
|
100
|
+
│ tandem keeps the other sessions in sync │
|
|
101
|
+
│ locally — without calling another model │
|
|
102
|
+
└──────────────────────┬───────────────────────┘
|
|
103
|
+
▼
|
|
104
|
+
┌──────────────────────────────────────────────┐
|
|
105
|
+
│ Press Ctrl-] and continue in the next CLI │
|
|
106
|
+
└──────────────────────────────────────────────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Only the active model runs. tandem translates its growing conversation into
|
|
110
|
+
the other CLIs' native session formats using local file access, so they are
|
|
111
|
+
ready when you switch. Your session stays in the CLIs' own storage plus a
|
|
112
|
+
small local database in `~/.tandem`; tandem adds no cloud sync or telemetry.
|
|
113
|
+
|
|
114
|
+
See [How tandem works](https://github.com/Bhavya6187/tandem/blob/main/docs/how-it-works.md)
|
|
115
|
+
for transcript translation, switching, crash safety, compatibility, and data
|
|
116
|
+
locations.
|
|
117
|
+
|
|
118
|
+
## Everyday commands
|
|
119
|
+
|
|
120
|
+
| Command | What it does |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `tandem` | Start a new session |
|
|
123
|
+
| `Ctrl-]` | Continue in the next CLI |
|
|
124
|
+
| `tandem resume [id]` | Resume the latest or a specific session in this directory |
|
|
125
|
+
| `tandem sessions [-n N]` | List recent sessions across directories |
|
|
126
|
+
| `tandem run --on codex "…"` | Send one contextual prompt to another CLI (`claude`, `codex`, or `opencode`) |
|
|
127
|
+
|
|
128
|
+
## Learn more
|
|
129
|
+
|
|
130
|
+
- [Why tandem?](https://github.com/Bhavya6187/tandem/blob/main/docs/why.md) —
|
|
131
|
+
use cases, subscriptions, native tools, token visibility, and privacy
|
|
132
|
+
- [GPT subagents](https://github.com/Bhavya6187/tandem/blob/main/docs/subagents.md) —
|
|
133
|
+
plugin setup, worker models, routing, and sandboxing
|
|
134
|
+
- [Configuration](https://github.com/Bhavya6187/tandem/blob/main/docs/configuration.md) —
|
|
135
|
+
participants, startup arguments, the switch key, and the status bar
|
|
136
|
+
- [How tandem works](https://github.com/Bhavya6187/tandem/blob/main/docs/how-it-works.md) —
|
|
137
|
+
synchronization, switching, compatibility, and local data
|
|
138
|
+
- [Developing tandem](https://github.com/Bhavya6187/tandem/blob/main/docs/development.md) —
|
|
139
|
+
development setup and the harness adapter interface
|
|
140
|
+
- [Observed session formats](https://github.com/Bhavya6187/tandem/blob/main/docs/formats.md) —
|
|
141
|
+
Claude Code, Codex, and opencode storage formats
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
[MIT](https://github.com/Bhavya6187/tandem/blob/main/LICENSE)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🤝 tandem
|
|
4
|
+
|
|
5
|
+
**One coding session. Multiple AI agents. Shared context.**
|
|
6
|
+
|
|
7
|
+
Use [Claude Code](https://docs.anthropic.com/en/docs/claude-code),
|
|
8
|
+
[OpenAI Codex CLI](https://github.com/openai/codex), and
|
|
9
|
+
[opencode](https://opencode.ai) in one continuous coding session. Work in
|
|
10
|
+
their native interfaces, press **Ctrl-]** to move to the next CLI, and
|
|
11
|
+
continue without copying prompts or rebuilding context by hand.
|
|
12
|
+
|
|
13
|
+
[](https://github.com/Bhavya6187/tandem/actions/workflows/ci.yml)
|
|
14
|
+
[](https://pypi.org/project/tandem-cli/)
|
|
15
|
+
[](https://github.com/Bhavya6187/tandem/blob/main/pyproject.toml)
|
|
16
|
+
[](https://github.com/Bhavya6187/tandem/blob/main/LICENSE)
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
You need Python 3.11+ and at least two supported CLIs installed and signed
|
|
25
|
+
in: [Claude Code](https://docs.anthropic.com/en/docs/claude-code),
|
|
26
|
+
[Codex CLI](https://github.com/openai/codex), or
|
|
27
|
+
[opencode](https://opencode.ai).
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv tool install tandem-cli # or: pip install tandem-cli
|
|
31
|
+
cd your-project
|
|
32
|
+
tandem
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Work normally in the CLI that opens. Press **Ctrl-]** to continue the same
|
|
36
|
+
session in the next CLI. If you press it while the model is working, tandem
|
|
37
|
+
waits until the response finishes; press it again to cancel the switch.
|
|
38
|
+
|
|
39
|
+
Exit normally when you're done. Come back later with:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
tandem resume # resume the latest session in this directory
|
|
43
|
+
tandem sessions # find recent sessions across directories
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
By default, tandem opens the first usable CLI in your configured order. Use
|
|
47
|
+
`tandem --active codex` or `tandem --active opencode` to choose another.
|
|
48
|
+
|
|
49
|
+
## Why tandem?
|
|
50
|
+
|
|
51
|
+
- **Keep going when a model hits its limit.** Move to another CLI and
|
|
52
|
+
continue with the same files and conversation history.
|
|
53
|
+
- **Bring different models to the same problem.** Ask another CLI for a
|
|
54
|
+
second opinion without copying a wall of context between terminals.
|
|
55
|
+
- **Keep the native tools you already use.** Claude Code, Codex, and
|
|
56
|
+
opencode retain their own interfaces, commands, keybindings, and MCP
|
|
57
|
+
servers.
|
|
58
|
+
- **Work through a single-provider outage.** If another configured CLI is
|
|
59
|
+
available, switch and continue until the affected provider recovers.
|
|
60
|
+
|
|
61
|
+
Want GPT subagents inside Claude Code too? See the
|
|
62
|
+
[GPT subagents guide](https://github.com/Bhavya6187/tandem/blob/main/docs/subagents.md).
|
|
63
|
+
For more use cases, see
|
|
64
|
+
[Why tandem?](https://github.com/Bhavya6187/tandem/blob/main/docs/why.md).
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
┌──────────────────────────────────────────────┐
|
|
70
|
+
│ Work normally in a native coding CLI │
|
|
71
|
+
└──────────────────────┬───────────────────────┘
|
|
72
|
+
▼
|
|
73
|
+
┌──────────────────────────────────────────────┐
|
|
74
|
+
│ tandem keeps the other sessions in sync │
|
|
75
|
+
│ locally — without calling another model │
|
|
76
|
+
└──────────────────────┬───────────────────────┘
|
|
77
|
+
▼
|
|
78
|
+
┌──────────────────────────────────────────────┐
|
|
79
|
+
│ Press Ctrl-] and continue in the next CLI │
|
|
80
|
+
└──────────────────────────────────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Only the active model runs. tandem translates its growing conversation into
|
|
84
|
+
the other CLIs' native session formats using local file access, so they are
|
|
85
|
+
ready when you switch. Your session stays in the CLIs' own storage plus a
|
|
86
|
+
small local database in `~/.tandem`; tandem adds no cloud sync or telemetry.
|
|
87
|
+
|
|
88
|
+
See [How tandem works](https://github.com/Bhavya6187/tandem/blob/main/docs/how-it-works.md)
|
|
89
|
+
for transcript translation, switching, crash safety, compatibility, and data
|
|
90
|
+
locations.
|
|
91
|
+
|
|
92
|
+
## Everyday commands
|
|
93
|
+
|
|
94
|
+
| Command | What it does |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `tandem` | Start a new session |
|
|
97
|
+
| `Ctrl-]` | Continue in the next CLI |
|
|
98
|
+
| `tandem resume [id]` | Resume the latest or a specific session in this directory |
|
|
99
|
+
| `tandem sessions [-n N]` | List recent sessions across directories |
|
|
100
|
+
| `tandem run --on codex "…"` | Send one contextual prompt to another CLI (`claude`, `codex`, or `opencode`) |
|
|
101
|
+
|
|
102
|
+
## Learn more
|
|
103
|
+
|
|
104
|
+
- [Why tandem?](https://github.com/Bhavya6187/tandem/blob/main/docs/why.md) —
|
|
105
|
+
use cases, subscriptions, native tools, token visibility, and privacy
|
|
106
|
+
- [GPT subagents](https://github.com/Bhavya6187/tandem/blob/main/docs/subagents.md) —
|
|
107
|
+
plugin setup, worker models, routing, and sandboxing
|
|
108
|
+
- [Configuration](https://github.com/Bhavya6187/tandem/blob/main/docs/configuration.md) —
|
|
109
|
+
participants, startup arguments, the switch key, and the status bar
|
|
110
|
+
- [How tandem works](https://github.com/Bhavya6187/tandem/blob/main/docs/how-it-works.md) —
|
|
111
|
+
synchronization, switching, compatibility, and local data
|
|
112
|
+
- [Developing tandem](https://github.com/Bhavya6187/tandem/blob/main/docs/development.md) —
|
|
113
|
+
development setup and the harness adapter interface
|
|
114
|
+
- [Observed session formats](https://github.com/Bhavya6187/tandem/blob/main/docs/formats.md) —
|
|
115
|
+
Claude Code, Codex, and opencode storage formats
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
[MIT](https://github.com/Bhavya6187/tandem/blob/main/LICENSE)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
tandem reads one optional file: `~/.tandem/config.toml`. No file is
|
|
4
|
+
required; every key has a working default, and a malformed value falls
|
|
5
|
+
back to it rather than failing a launch. (Back to the
|
|
6
|
+
[README](../README.md).)
|
|
7
|
+
|
|
8
|
+
## harnesses — who participates, and in what order
|
|
9
|
+
|
|
10
|
+
A top-level list naming the harnesses a fresh session may include, in
|
|
11
|
+
flip-cycle order. Default: all three, claude first.
|
|
12
|
+
|
|
13
|
+
```toml
|
|
14
|
+
harnesses = ["claude", "codex", "opencode"]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Naming a harness here is an *intent*, not a requirement: a fresh
|
|
18
|
+
`tandem` session pairs the listed harnesses that are actually installed
|
|
19
|
+
and usable — not-installed ones are skipped silently, ones that are
|
|
20
|
+
installed but unusable warn and drop out, and fewer than two usable is
|
|
21
|
+
an error that prints the install command for each missing CLI. The
|
|
22
|
+
order sets both the **Ctrl-]** cycle and the default starting harness
|
|
23
|
+
(`tandem` enters the first usable one; `--active` overrides that per
|
|
24
|
+
launch).
|
|
25
|
+
Leave a harness off the list to keep it out of new sessions even though
|
|
26
|
+
it's installed — `harnesses = ["claude", "codex"]` gives two-way
|
|
27
|
+
sessions on a machine that also has opencode. Unknown names are
|
|
28
|
+
dropped, duplicates deduped, and anything else malformed falls back to
|
|
29
|
+
all three. Sessions already paired keep their own participant list.
|
|
30
|
+
|
|
31
|
+
## [subagents] — GPT subagent workers
|
|
32
|
+
|
|
33
|
+
Worker model, routing mode, and context handling for GPT subagent
|
|
34
|
+
dispatches. Key semantics and the full routing story live in the
|
|
35
|
+
[GPT subagents guide](subagents.md):
|
|
36
|
+
|
|
37
|
+
```toml
|
|
38
|
+
[subagents]
|
|
39
|
+
model = "gpt-5.6-luna" # worker default; unset = your codex account's default
|
|
40
|
+
route = "manual" # manual | all | off
|
|
41
|
+
context = "match" # match | task | full
|
|
42
|
+
keep_forks = false # keep each worker's rollout for debugging
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## [claude] / [codex] / [opencode] — per-harness startup args
|
|
46
|
+
|
|
47
|
+
Optional per-harness tables add flags to every interactive session tandem
|
|
48
|
+
opens (`tandem`, `tandem resume`, and each flip) — one-off relays
|
|
49
|
+
(`tandem run`), subagent dispatch, and doctor probes are unaffected:
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[claude]
|
|
53
|
+
args = ["--dangerously-skip-permissions"]
|
|
54
|
+
|
|
55
|
+
[codex]
|
|
56
|
+
args = ["--dangerously-bypass-approvals-and-sandbox"]
|
|
57
|
+
|
|
58
|
+
[opencode]
|
|
59
|
+
args = [] # same mechanism; opencode's own flags go here
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The claude/codex flags shown disable the harnesses' own permission
|
|
63
|
+
prompts for sessions tandem launches — set them only if that is what you
|
|
64
|
+
want.
|
|
65
|
+
The list is passed to the harness raw: a flag that expects a value can
|
|
66
|
+
swallow the settings tandem appends after it and break turn tracking.
|
|
67
|
+
Malformed values (a non-list, empty or non-string elements) are
|
|
68
|
+
silently ignored rather than failing the launch.
|
|
69
|
+
|
|
70
|
+
## [frame] — the flip key, the tab bar, and warm flips
|
|
71
|
+
|
|
72
|
+
The frame is tandem's own surface inside a running session: one reserved
|
|
73
|
+
keybind that flips to the next harness in the cycle, the one-line tab
|
|
74
|
+
bar on the bottom terminal row (participants, flip key, and the active
|
|
75
|
+
model's live token stats), and the pipelined boot behind the flip.
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[frame]
|
|
79
|
+
flip_key = "ctrl-]"
|
|
80
|
+
bar = true
|
|
81
|
+
warm = true # boot the incoming harness while the outgoing one shuts down
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| key | default | meaning |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `flip_key` | `"ctrl-]"` | The flip keybind, consumed by tandem (never forwarded). Accepts `ctrl-<char>` or a hex byte like `"0x1d"`; printable keys are rejected (they would swallow typing). The bar relabels itself to match (`ctrl-t` shows `^T flips`). |
|
|
87
|
+
| `bar` | `true` | The one-line tab bar on the bottom terminal row, including the active slot's token stats. `false` hides it; the flip still works. |
|
|
88
|
+
| `warm` | `true` | Overlap the two halves of a flip: the incoming harness starts booting the moment the flip fires (a mid-turn press waits for the turn boundary first), while the outgoing one is still shutting down. `false` gives fully serial flips — the boot only begins once the old harness is gone. Flips *into* opencode are always serial (its TUI must open after the last turn has landed in its database). |
|
|
89
|
+
|
|
90
|
+
An unparseable value falls back to the default rather than failing the
|
|
91
|
+
launch. If a terminal can't sustain the bar, tandem drops it for the rest
|
|
92
|
+
of that session — the flip is unaffected — and `tandem doctor` warns
|
|
93
|
+
about it until you delete the marker file it names; set `bar = false` if
|
|
94
|
+
you'd rather keep the bar off for good. Shrinking the window below the
|
|
95
|
+
bar's row floor also drops it for the session, but that is tandem's own
|
|
96
|
+
policy rather than a conflict, so nothing is recorded and `doctor` stays
|
|
97
|
+
quiet.
|
|
98
|
+
|
|
99
|
+
Warming is pipelining, not a background service: the moment the flip
|
|
100
|
+
fires — a mid-turn `Ctrl-]` waits for the turn boundary first — the
|
|
101
|
+
incoming harness starts and the outgoing one is torn down at the same
|
|
102
|
+
time, so the flip lands at about the incoming harness's own start-up
|
|
103
|
+
speed instead of that plus the shutdown. Nothing exists before you press
|
|
104
|
+
the key and nothing survives the flip — between flips a tandem session is
|
|
105
|
+
exactly one harness process. `warm = false` restores the fully serial
|
|
106
|
+
flip, which is slower but does the same thing.
|
|
107
|
+
|
|
108
|
+
## Environment variables
|
|
109
|
+
|
|
110
|
+
Not config keys, but honored everywhere: `TANDEM_HOME` relocates
|
|
111
|
+
tandem's own state (`state.db`, config, quarantine, subagent logs) from
|
|
112
|
+
`~/.tandem`; the harnesses' own overrides — `CLAUDE_CONFIG_DIR`,
|
|
113
|
+
`CODEX_HOME`, `OPENCODE_DB` — are respected when tandem looks for their
|
|
114
|
+
session stores.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Developing tandem
|
|
2
|
+
|
|
3
|
+
Dev setup and the extension surface. (Back to the
|
|
4
|
+
[README](../README.md).)
|
|
5
|
+
|
|
6
|
+
## Development
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
uv sync && uv run pytest
|
|
10
|
+
uv tool install . # or: pipx install .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Dependencies are deliberately small: `click` (CLI), `pydantic` v2 (event
|
|
14
|
+
schema), `watchdog` (transcript tailing), `pexpect`/ptyprocess (PTY
|
|
15
|
+
passthrough); state is stdlib `sqlite3`.
|
|
16
|
+
|
|
17
|
+
The suite is hermetic — no harness binaries needed — except
|
|
18
|
+
`tests/test_opencode_oracle.py`, which round-trips through a real
|
|
19
|
+
`opencode` binary against a throwaway database (`OPENCODE_DB`) and skips
|
|
20
|
+
itself when the binary is absent. Two guards worth knowing about:
|
|
21
|
+
`plugin/.claude-plugin/plugin.json`'s `version` must match
|
|
22
|
+
`pyproject.toml` (a drift test fails otherwise — see the
|
|
23
|
+
[plugin README](../plugin/README.md) for why), and CI installs with
|
|
24
|
+
`uv sync --locked`, so a version bump needs `uv lock` in the same
|
|
25
|
+
commit.
|
|
26
|
+
|
|
27
|
+
## Layout
|
|
28
|
+
|
|
29
|
+
- `src/tandem/cli.py` — every subcommand; `_resolve_participants` is the
|
|
30
|
+
one place that decides which harnesses a session gets.
|
|
31
|
+
- `src/tandem/harness/` — one adapter module per CLI (`claude_code.py`,
|
|
32
|
+
`codex.py`, `opencode.py`) behind `base.HarnessAdapter`; **all**
|
|
33
|
+
session-format knowledge lives here, so a format change in one tool
|
|
34
|
+
touches one file. `docs/formats.md` records what each adapter was
|
|
35
|
+
built against.
|
|
36
|
+
- `src/tandem/sync.py`, `runner.py`, `tailer.py` — the sync engine: one
|
|
37
|
+
tail loop per (source → target) direction, append-only with a
|
|
38
|
+
write-ahead intent in the cursor.
|
|
39
|
+
- `src/tandem/events.py`, `converter.py`, `toolmap.py` — the normalized
|
|
40
|
+
event model, the reference converter, and native tool-call mapping.
|
|
41
|
+
- `src/tandem/flip.py`, `frame.py`, `ptyrun.py`, `warm.py` — the frame:
|
|
42
|
+
PTY passthrough, the flip key and ladder, the tab bar, pipelined boots.
|
|
43
|
+
- `src/tandem/hookroute.py`, `modelcat.py`, `pinstash.py`,
|
|
44
|
+
`plugin_setup.py` — GPT subagents: the Claude Code hook, model-name
|
|
45
|
+
resolution, and plugin install.
|
|
46
|
+
- `src/tandem/state.py`, `compat.py`, `config.py`, `doctor.py` — the
|
|
47
|
+
SQLite state store, pinned version ranges, `config.toml`, health checks.
|
|
48
|
+
|
|
49
|
+
## Extending tandem
|
|
50
|
+
|
|
51
|
+
### Adding a harness
|
|
52
|
+
|
|
53
|
+
Every CLI tandem fronts is one `HarnessAdapter` subclass in
|
|
54
|
+
`src/tandem/harness/`, registered in `harness/__init__.py`'s `ADAPTERS`
|
|
55
|
+
and named in `config.SUPPORTED_HARNESSES` and `compat.COMPAT`. The base
|
|
56
|
+
class in `harness/base.py` is the contract; the pieces an adapter
|
|
57
|
+
provides:
|
|
58
|
+
|
|
59
|
+
- **Detection and readiness** — `detect_version`, `version_supported`
|
|
60
|
+
(against its `compat.COMPAT` range), `runtime_ready` (fail closed:
|
|
61
|
+
installed but unusable drops out of the session with a reason), and
|
|
62
|
+
an `install_hint` for the "fewer than two harnesses" error.
|
|
63
|
+
- **Session identity and storage** — `mint_session_id`,
|
|
64
|
+
`transcript_path`, `create_shadow_transcript` (how a resume-ready
|
|
65
|
+
shadow is born), plus the storage-capability hooks that default to
|
|
66
|
+
file-backed JSONL and that opencode overrides for SQLite:
|
|
67
|
+
`make_source_reader`, `watch_paths`, `shadow_append` /
|
|
68
|
+
`shadow_intent` / `intent_landed`, `fast_forward_cursor`,
|
|
69
|
+
`pending_units`, `prepare_shadow`.
|
|
70
|
+
- **Launch** — `interactive_argv`, `oneoff_argv`, `hook_argv_extra`
|
|
71
|
+
(the per-invocation turn-complete signal, if the CLI has one),
|
|
72
|
+
`quit_keystrokes` (the graceful half of the exit ladder), and
|
|
73
|
+
`session_status` when the CLI can answer "is a turn running?" — that
|
|
74
|
+
is what lets a mid-turn flip fire at the boundary.
|
|
75
|
+
- **Translation** — `parse_entry` (native record → normalized events)
|
|
76
|
+
and `render_events` / `render_placeholder` (normalized events → native
|
|
77
|
+
records), with `toolmap` optionally given a Tier-1 table so common tool
|
|
78
|
+
calls read as the harness's own; without one, calls pass through
|
|
79
|
+
verbatim.
|
|
80
|
+
- **Cosmetics** — `make_usage_meter` for the tab bar's token stats.
|
|
81
|
+
|
|
82
|
+
`tests/` has per-adapter fixtures; an oracle test against the real binary
|
|
83
|
+
(like the opencode one) is the strongest check that shadow sessions
|
|
84
|
+
actually resume.
|
|
85
|
+
|
|
86
|
+
### Swapping the converter
|
|
87
|
+
|
|
88
|
+
The sync engine talks to a small adapter interface
|
|
89
|
+
(`tandem.converter.TraceConverter`):
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
class TraceConverter(Protocol):
|
|
93
|
+
def translate_entry(entry, direction, ctx) -> list[TargetEntry] | TranslationError
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`ReferenceConverter` implements it via a normalized event model
|
|
97
|
+
(`tandem/events.py`) derived from the observed formats. Pass your own
|
|
98
|
+
converter to `SyncEngine(store, session, source, target, converter=...)`.
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# Observed session formats
|
|
2
2
|
|
|
3
|
-
Everything tandem knows about the
|
|
4
|
-
this machine by creating throwaway sessions and reading the files
|
|
5
|
-
wrote. Versions observed:
|
|
3
|
+
Everything tandem knows about the three native session formats was observed
|
|
4
|
+
on this machine by creating throwaway sessions and reading the files (or
|
|
5
|
+
database) the CLIs wrote. Versions observed:
|
|
6
6
|
|
|
7
7
|
| CLI | version | session storage |
|
|
8
8
|
| --- | --- | --- |
|
|
9
9
|
| Claude Code (`claude`) | 2.1.220 | `~/.claude/projects/<munged-cwd>/<sessionId>.jsonl` |
|
|
10
10
|
| Codex CLI (`codex`) | 0.145.0 | `~/.codex/sessions/YYYY/MM/DD/rollout-<YYYY-MM-DDThh-mm-ss>-<uuidv7>.jsonl` |
|
|
11
|
+
| opencode (`opencode`) | 1.18.15 | one SQLite database, the path `opencode db path` prints (see below) |
|
|
11
12
|
|
|
12
13
|
Env overrides honored: `CLAUDE_CONFIG_DIR` (claude home), `CODEX_HOME` (codex
|
|
13
|
-
home), `TANDEM_HOME` (tandem state).
|
|
14
|
+
home), `OPENCODE_DB` (opencode database), `TANDEM_HOME` (tandem state).
|
|
14
15
|
|
|
15
16
|
## Claude Code transcript (claude 2.1.220)
|
|
16
17
|
|
|
@@ -110,3 +111,38 @@ home), `TANDEM_HOME` (tandem state).
|
|
|
110
111
|
raw entry quarantined at {path}]`
|
|
111
112
|
with the raw source entry written to
|
|
112
113
|
`~/.tandem/quarantine/<tandem-id>/<source>-line-<n>.json`.
|
|
114
|
+
|
|
115
|
+
## opencode session storage (opencode 1.18.15)
|
|
116
|
+
|
|
117
|
+
- Storage: one WAL-mode SQLite DB for everything — `opencode db path`
|
|
118
|
+
(channel-suffixed filename; `$OPENCODE_DB` overrides). No per-session
|
|
119
|
+
files. Tables: `session`, `message`, `part`; JSON payloads in `data`
|
|
120
|
+
minus the id/fk columns. FKs cascade part -> message -> session -> project.
|
|
121
|
+
- Ordering: messages by `(time_created, id)`; parts by part id ONLY.
|
|
122
|
+
- IDs: `<prefix>_` + 12 hex chars (48-bit `ms*4096+counter`) + 14 random
|
|
123
|
+
base62. `ses_` NOTs the value (descending); `msg_`/`prt_` ascending.
|
|
124
|
+
Live-verified: msg @ ms=1786577389138 ctr=1 -> `ff84f8652001`.
|
|
125
|
+
- Threading is flat: assistant `parentID` = the turn's user message id.
|
|
126
|
+
- Spelling trap: session-row `model` JSON uses `{id, providerID}`; message
|
|
127
|
+
payloads use `{modelID, providerID}`. Session-level `agent`/`model` are
|
|
128
|
+
optional; MESSAGE-level are required on user messages. Session-level
|
|
129
|
+
`slug` is REQUIRED by import's decoder (oracle-verified: the payload is
|
|
130
|
+
rejected `at ["slug"]` without it) but carries no uniqueness constraint —
|
|
131
|
+
opencode mints adjective-noun pairs; tandem seeds `tandem-pair`.
|
|
132
|
+
- Tool parts mutate in place (pending -> running -> completed): tandem reads
|
|
133
|
+
whole completed turns only (last assistant has `time.completed` + terminal
|
|
134
|
+
`finish`).
|
|
135
|
+
- External writes: the `opencode import` recipe (plain INSERT, conflict-
|
|
136
|
+
ignore). Tandem's shadow birth delegates to `opencode import`; incremental
|
|
137
|
+
sync writes rows directly with pre-minted ids (idempotent replay).
|
|
138
|
+
- Tandem attribution: `providerID: "tandem"`, `modelID: "<synced>"` — marks
|
|
139
|
+
echoes for the parser and makes opencode degrade replay metadata instead
|
|
140
|
+
of re-sending forged provider signatures.
|
|
141
|
+
- Hazards: a running TUI never sees external rows for an already-synced
|
|
142
|
+
session (writes land while opencode is closed; flips relaunch it); a
|
|
143
|
+
session whose last message is not a completed assistant renders as
|
|
144
|
+
perpetually "working"; the session list window is 30 days by
|
|
145
|
+
`time_updated`; always open the DB read-write (WAL).
|
|
146
|
+
- Resume: `opencode -s <id>` (id must exist; no directory match);
|
|
147
|
+
one-off: `opencode run -s <id> "<prompt>"`. No per-invocation
|
|
148
|
+
turn-complete hook — tandem fs-watches the `-wal` file.
|