I was following Pi agent development, and intrigued by oh-my-pi, but decided to start building on top of opencode instead.
I am starting to regret that decision, but will see how far it goes.
I absolutely loved this blog post https://blog.can.ac/2026/02/12/the-harness-problem/ because I see failed edits constantly with Local LLMs, and this is a novel approach.
Is it very novel though? BASIC had lines that start with indexes, too. I want to use it, anyway. GOTO 200.
200 I am attempting to create a CLI version of this edit tool for later opencode integration, but it might stand alone. This was initially ported from oh-my-pi with LLM help into an opencode tool, then later extracted as a Rust CLI.
Run cargo install --path . or create a wrapper script called 'hashline-tools' over cargo run.
Copy the typescript wrappers in ./opencode-tools to ~/.config/opencode/tools or a specific project folder's .opencode.
After applying edits, the tool returns a custom diff format that includes freshly calculated hashes for immediate reuse:
Edit applied successfully (first change at line 8).
<diff>
--- /tmp/file.txt
+++ /tmp/file.txt
3#NQ:line 3
4#RH:line 4
5#XH:line 5
6#ZT:line 6
7#BX:line 7
-8# :line 8
+8#RT:modified line 8
9#PJ:line 9
10#NV:line 10
Note: Lines after edited regions have stale hashes. Use hashread to refresh.
</diff>
Format:
LINE#HASH:contentfor context and inserted lines (fresh hashes)LINE# :contentfor deleted lines (no hash, shown as 2 spaces)±5lines of context around each change- Shows
...for gaps between change regions - Terse note: "Lines after edited regions have stale hashes. Use hashread to refresh."
This allows successive edits without an intermediate hashread call - just copy the LINE#HASH anchor from the diff output.
This implementation diverges from oh-my-pi in several ways:
Original: {line: 8, hash: "RT"} (object)
This implementation: "8#RT" (string)
The string format aligns with:
- hashread output:
8#RT:line content - hashedit input:
"pos": "8#RT" - diff output:
+8#RT:modified content
Replace single line:
{
"op": "replace",
"pos": "8#RT",
"lines": ["new content"]
}Replace range:
{
"op": "replace",
"pos": "6#ZT",
"end": "10#NV",
"lines": ["replaced content"]
}Append after line:
{
"op": "append",
"pos": "8#RT",
"lines": ["new line after"]
}Append to EOF:
{
"op": "append",
"lines": ["new line at end"]
}Prepend before line:
{
"op": "prepend",
"pos": "8#RT",
"lines": ["new line before"]
}Prepend to BOF:
{
"op": "prepend",
"lines": ["new line at start"]
}Delete single line:
{
"op": "delete",
"pos": "8#RT"
}Delete range:
{
"op": "delete",
"pos": "6#ZT",
"end": "10#NV"
}- Diffs are not easily displayed in the opencode TUI due to external tool restrictions anomalyco/opencode#6831 (comment)