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.
Files changed (125) hide show
  1. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.claude/settings.local.json +6 -1
  2. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.gitignore +3 -0
  3. tandem_cli-0.4.0/PKG-INFO +145 -0
  4. tandem_cli-0.4.0/README.md +119 -0
  5. tandem_cli-0.4.0/docs/configuration.md +114 -0
  6. tandem_cli-0.4.0/docs/development.md +98 -0
  7. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/formats.md +40 -4
  8. tandem_cli-0.4.0/docs/how-it-works.md +161 -0
  9. tandem_cli-0.4.0/docs/plans/2026-08-09-meta-harness-frame.md +1860 -0
  10. tandem_cli-0.4.0/docs/plans/2026-08-09-readme-restructure.md +367 -0
  11. tandem_cli-0.4.0/docs/plans/2026-08-10-idle-flip-status-probe.md +595 -0
  12. tandem_cli-0.4.0/docs/plans/2026-08-10-retire-interstitial-prompt.md +857 -0
  13. tandem_cli-0.4.0/docs/plans/2026-08-13-pr1-nharness-core.md +1813 -0
  14. tandem_cli-0.4.0/docs/plans/2026-08-13-pr2-opencode-adapter.md +1907 -0
  15. tandem_cli-0.4.0/docs/specs/2026-08-09-meta-harness-frame-design.md +186 -0
  16. tandem_cli-0.4.0/docs/specs/2026-08-09-model-pin-recovery-design.md +140 -0
  17. tandem_cli-0.4.0/docs/specs/2026-08-09-readme-restructure-design.md +117 -0
  18. tandem_cli-0.4.0/docs/specs/2026-08-10-remove-interstitial-prompt-design.md +102 -0
  19. tandem_cli-0.4.0/docs/specs/2026-08-13-opencode-harness-design.md +442 -0
  20. tandem_cli-0.4.0/docs/subagents.md +139 -0
  21. tandem_cli-0.4.0/docs/superpowers/plans/2026-08-11-process-warmup-live-validation.md +97 -0
  22. tandem_cli-0.4.0/docs/superpowers/plans/2026-08-11-process-warmup.md +1706 -0
  23. tandem_cli-0.4.0/docs/superpowers/plans/2026-08-12-process-warmup-pipelined.md +286 -0
  24. tandem_cli-0.4.0/docs/superpowers/specs/2026-08-10-idle-flip-status-probe.md +101 -0
  25. tandem_cli-0.4.0/docs/superpowers/specs/2026-08-11-process-warmup-design.md +221 -0
  26. tandem_cli-0.4.0/docs/superpowers/specs/2026-08-12-process-warmup-pipelined-design.md +125 -0
  27. tandem_cli-0.4.0/docs/why.md +89 -0
  28. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/.claude-plugin/plugin.json +1 -1
  29. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/README.md +31 -12
  30. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/agents/codex-worker.md +8 -0
  31. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/agents/gpt.md +8 -0
  32. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/pyproject.toml +1 -1
  33. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/cli.py +254 -119
  34. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/compat.py +7 -2
  35. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/config.py +66 -1
  36. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/constants.py +1 -0
  37. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/converter.py +5 -8
  38. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/doctor.py +53 -129
  39. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/events.py +30 -15
  40. tandem_cli-0.4.0/src/tandem/flip.py +325 -0
  41. tandem_cli-0.4.0/src/tandem/frame.py +287 -0
  42. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/harness/__init__.py +2 -4
  43. tandem_cli-0.4.0/src/tandem/harness/base.py +267 -0
  44. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/harness/claude_code.py +176 -12
  45. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/harness/codex.py +83 -1
  46. tandem_cli-0.4.0/src/tandem/harness/opencode.py +772 -0
  47. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/hookroute.py +36 -0
  48. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/memory_sync.py +2 -0
  49. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/ops.py +131 -70
  50. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/paths.py +8 -0
  51. tandem_cli-0.4.0/src/tandem/pinstash.py +97 -0
  52. tandem_cli-0.4.0/src/tandem/ptyrun.py +446 -0
  53. tandem_cli-0.4.0/src/tandem/runner.py +847 -0
  54. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/state.py +93 -59
  55. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/sync.py +27 -54
  56. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/tailer.py +12 -3
  57. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/toolmap.py +8 -3
  58. tandem_cli-0.4.0/src/tandem/warm.py +172 -0
  59. tandem_cli-0.4.0/tests/conftest.py +280 -0
  60. tandem_cli-0.4.0/tests/test_cli.py +347 -0
  61. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_config.py +73 -1
  62. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_converter.py +11 -5
  63. tandem_cli-0.4.0/tests/test_flip.py +586 -0
  64. tandem_cli-0.4.0/tests/test_frame.py +588 -0
  65. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_hookroute.py +65 -0
  66. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_memory_doctor.py +37 -1
  67. tandem_cli-0.4.0/tests/test_opencode.py +529 -0
  68. tandem_cli-0.4.0/tests/test_opencode_oracle.py +100 -0
  69. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_ops.py +122 -12
  70. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_parse.py +41 -2
  71. tandem_cli-0.4.0/tests/test_participants.py +135 -0
  72. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_paths_compat.py +16 -0
  73. tandem_cli-0.4.0/tests/test_pinstash.py +94 -0
  74. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_plugin.py +8 -1
  75. tandem_cli-0.4.0/tests/test_ptyrun.py +582 -0
  76. tandem_cli-0.4.0/tests/test_runner.py +1547 -0
  77. tandem_cli-0.4.0/tests/test_state.py +234 -0
  78. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_sub.py +120 -28
  79. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_sync.py +25 -3
  80. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_tail.py +38 -6
  81. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_toolmap.py +27 -10
  82. tandem_cli-0.4.0/tests/test_usage.py +274 -0
  83. tandem_cli-0.4.0/tests/test_warm.py +223 -0
  84. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/uv.lock +1 -1
  85. tandem_cli-0.2.0/.superpowers/sdd/.gitignore +0 -1
  86. tandem_cli-0.2.0/PKG-INFO +0 -389
  87. tandem_cli-0.2.0/README.md +0 -363
  88. tandem_cli-0.2.0/docs/gpt-subagent.gif +0 -0
  89. tandem_cli-0.2.0/src/tandem/harness/base.py +0 -87
  90. tandem_cli-0.2.0/src/tandem/ptyrun.py +0 -99
  91. tandem_cli-0.2.0/src/tandem/runner.py +0 -261
  92. tandem_cli-0.2.0/src/tandem/shell.py +0 -207
  93. tandem_cli-0.2.0/tests/conftest.py +0 -142
  94. tandem_cli-0.2.0/tests/test_cli.py +0 -205
  95. tandem_cli-0.2.0/tests/test_ptyrun.py +0 -14
  96. tandem_cli-0.2.0/tests/test_runner.py +0 -104
  97. tandem_cli-0.2.0/tests/test_shell.py +0 -402
  98. tandem_cli-0.2.0/tests/test_state.py +0 -147
  99. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.claude-plugin/marketplace.json +0 -0
  100. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/.github/workflows/ci.yml +0 -0
  101. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/LICENSE +0 -0
  102. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-07-29-native-tool-call-translation.md +0 -0
  103. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-07-31-codex-subagents.md +0 -0
  104. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-01-plugin-marketplace-install.md +0 -0
  105. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-01-sandbox-consent-and-manual-routing.md +0 -0
  106. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-02-plugin-auto-install.md +0 -0
  107. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-03-manual-default-model-passthrough.md +0 -0
  108. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/plans/2026-08-06-harness-startup-args.md +0 -0
  109. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-07-29-native-tool-call-translation-design.md +0 -0
  110. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-07-31-codex-subagents-design.md +0 -0
  111. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-08-02-plugin-auto-install-design.md +0 -0
  112. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-08-03-manual-default-model-passthrough-design.md +0 -0
  113. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/specs/2026-08-06-harness-startup-args-design.md +0 -0
  114. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/superpowers/plans/2026-07-29-tandem-shell.md +0 -0
  115. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/docs/superpowers/specs/2026-07-29-tandem-shell-design.md +0 -0
  116. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/plugin/hooks/hooks.json +0 -0
  117. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/__init__.py +0 -0
  118. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/modelcat.py +0 -0
  119. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/plugin_setup.py +0 -0
  120. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/summarize.py +0 -0
  121. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/src/tandem/util.py +0 -0
  122. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/golden/claude-probe.jsonl +0 -0
  123. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/golden/codex-probe.jsonl +0 -0
  124. {tandem_cli-0.2.0 → tandem_cli-0.4.0}/tests/test_modelcat.py +0 -0
  125. {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
  }
@@ -5,3 +5,6 @@ __pycache__/
5
5
  dist/
6
6
  build/
7
7
  .pytest_cache/
8
+
9
+ # superpowers brainstorm mockups
10
+ .superpowers/
@@ -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
+ [![CI](https://github.com/Bhavya6187/tandem/actions/workflows/ci.yml/badge.svg)](https://github.com/Bhavya6187/tandem/actions/workflows/ci.yml)
40
+ [![PyPI](https://img.shields.io/pypi/v/tandem-cli)](https://pypi.org/project/tandem-cli/)
41
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://github.com/Bhavya6187/tandem/blob/main/pyproject.toml)
42
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/Bhavya6187/tandem/blob/main/LICENSE)
43
+
44
+ ![tandem demo — one session relayed across Claude Code, Codex and opencode](https://raw.githubusercontent.com/Bhavya6187/tandem/main/docs/demo.gif)
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
+ [![CI](https://github.com/Bhavya6187/tandem/actions/workflows/ci.yml/badge.svg)](https://github.com/Bhavya6187/tandem/actions/workflows/ci.yml)
14
+ [![PyPI](https://img.shields.io/pypi/v/tandem-cli)](https://pypi.org/project/tandem-cli/)
15
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://github.com/Bhavya6187/tandem/blob/main/pyproject.toml)
16
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/Bhavya6187/tandem/blob/main/LICENSE)
17
+
18
+ ![tandem demo — one session relayed across Claude Code, Codex and opencode](https://raw.githubusercontent.com/Bhavya6187/tandem/main/docs/demo.gif)
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 two native session formats was observed on
4
- this machine by creating throwaway sessions and reading the files the CLIs
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.