Short description
When a PUT carries its body text on the same line as the address — PUT 15:<text>, with whitespace before the text — the call returns registers must begin with @, a rule about registers rather than about the body text the caller was writing. On the next line the equivalent mistake (a row without +) produces a clear, actionable error, and one payload can hold both a malformed inline row and correctly shaped + rows. A retry 35 s after the first rejection kept the inline row.
Companion report: #323 covers the other failure shape from the same model and tool — a symbol-mode call that never emitted content.
What happened?
Reproduction. Three probes, run through the pi surface against one scratch file. They were direct edit calls, not model output: each patch argument is exactly the block shown, with <TAG_FROM_READ> replaced by the tag from a tagged read of that file.
[example.txt#<TAG_FROM_READ>]
PUT 2: INLINE
→ registers must begin with @
[example.txt#<TAG_FROM_READ>]
PUT 2:
plain line without plus
→ invalid text PUT body row at patch line 3; expected `+<text>` for content or bare `+` for a blank row, but the row does not begin with `+`
[example.txt#<TAG_FROM_READ>]
PUT 2:INLINE_TEXT
→ "2:INLINE_TEXT" is not a valid positive line number
The three forms produce three different diagnostics. None of them is accepted, and the whitespace-separated inline form is the one that returns a rule about registers rather than about body rows.
Positive control. The same scratch file, same session, with the body on the following line:
[example.txt#<TAG_FROM_READ>]
PUT 2.=2:
+BETA_REPLACED
→ applied
PUT 2: followed by a + row on the next line also applied. AFT's description states that a plain N PUT replaces, so that form is supported here even though the reference implementation spells a single-line replacement PUT N.=N:. So the rejection above is about where the body starts, not about the text being unwritable through this tool.
Observed in a real session (OpenCode part.data, kimi-k3 through an OpenAI-compatible proxy, edit_mode: "hashline"). Four calls returned this message. Their three distinct malformed rows were:
PUT 15:<TAB><TAB>"sample": "cd <pkg> && bun run dev",
PUT 85:| phase-a | sample change | worker | done | gate 2 |
PUT 61:| phase-b | sample change | designer | pending | gate 2 |
(Payload contents are replaced with neutral samples; the shapes and line prefixes are unchanged.) Two of the four calls were a retry pair:
| call |
patch size |
returned |
| t=0 s |
2160 chars |
registers must begin with @ |
| t=35 s |
2159 chars |
registers must begin with @ |
They are near-identical rather than byte-identical — the second is one character shorter — and both keep the inline row. That establishes one retry, not a loop; these records do not show why the shape repeated.
Frequency. kimi-k3 edit calls in the patch-mode surface, 2026-09-18 23:21 through 2026-09-19 10:44 — 13 calls in one session. Two separate ad-hoc runs of the same model, variant and config (scratch project, one Markdown edit each) are not part of this count:
| outcome |
n |
registers must begin with @ (text on the address line) |
4 |
invalid text PUT body row at patch line N (row without a leading +) |
1 |
section tags must be exactly four hexadecimal digits (#LOCAL) |
1 |
addressed content no longer matches the … baseline |
1 |
Tool execution aborted (tool-level abort, not a parser rejection) |
1 |
| applied |
5 |
The five applied calls used PUT <addr>: with + rows on the following lines, so the documented shape is reachable for this model. The two ad-hoc runs mentioned above produced valid patches on the first try. That shows the model can emit the documented shape; it does not show why other calls used a different one. The abbreviated error strings in the table are normalized categories, not verbatim full texts.
Note on the format
This is not a request to accept the inline form. The section syntax, PUT/CUT operations, +TEXT body rows and colonless register paste are documented by @oh-my-pi/hashline, docs/tools/edit.md at 06aecdd5:
Only body-bearing PUT ...: headers take body rows. Every body row is +TEXT; + alone inserts a blank line.
PUT <N @name / PUT >N @name — paste a named register into a gap
PUT N.=M @name / PUT N* @name — replace a range or block with a named register
AFT documents the same body-row rule and the same colonless paste form. Its variant also accepts a plain line number as a single-line replacement — measured above, not inferred. This comparison establishes shared documented syntax for those two rules; it does not establish implementation identity or full compatibility between the two.
PUT 15:<text> therefore has no valid reading in either variant: the : form takes rows on the following lines, the paste form has no colon. Rejecting it is correct. What is missing is the diagnostic, and a published reference for the format.
A case-insensitive search of docs/tools.md at v0.56.2 found no occurrence of hashline. The reference implementation publishes the body-row rule above as part of its own prompt text and tool documentation. The observed calls include both valid and invalid body shapes; these records do not establish why the invalid ones were emitted or repeated.
Requests
- For the whitespace-separated inline-body failure, say that body rows follow the header and start with
+. Today the caller is told about a register rule; it needs the row rule. Rejecting the inline form itself is fine — that is this format's rule.
- Publish AFT's supported grammar: sections and tags, operations, address forms, body rows, registers.
docs/tools.md at v0.56.2 contains no hashline section.
- Document AFT's supported register targets, name characters and lifetime in the description or docs. Its only mention today is "PUT without
: copies @name (or the anonymous register) and takes no body".
- For the tag error: say where a tag comes from — copy the handle from a fresh tagged
read instead of inventing one or copying an example value.
Diagnostics
@cortexkit/aft-opencode 0.56.2
@cortexkit/aft-pi 0.56.2
bundled aft binary 0.56.2
aft binary md5 (both hosts) c00f08caf1672d5da2cfe2179e2bedd4
OpenCode host 1.18.31
Pi host 0.85.1
platform macOS 27.2 arm64
Relevant config (~/.config/cortexkit/aft.jsonc):
Model: kimi-k3 via an OpenAI-compatible proxy (reasoning_effort: "max").
Log output
Host-side record of one failing call, opened because the same payload holds a malformed inline row and a correctly shaped PUT >98: + +... row (OpenCode part.data; payload text replaced with neutral samples):
{ "type": "tool", "tool": "edit",
"state": { "status": "error",
"input": { "patch": "[example.txt#FCA9]\nPUT 85:| phase-a | sample change | worker | done | gate 2 |\nPUT >98:\n+sample text\n" },
"error": "registers must begin with @" } }
Short description
When a
PUTcarries its body text on the same line as the address —PUT 15:<text>, with whitespace before the text — the call returnsregisters must begin with @, a rule about registers rather than about the body text the caller was writing. On the next line the equivalent mistake (a row without+) produces a clear, actionable error, and one payload can hold both a malformed inline row and correctly shaped+rows. A retry 35 s after the first rejection kept the inline row.Companion report: #323 covers the other failure shape from the same model and tool — a symbol-mode call that never emitted
content.What happened?
Reproduction. Three probes, run through the pi surface against one scratch file. They were direct
editcalls, not model output: eachpatchargument is exactly the block shown, with<TAG_FROM_READ>replaced by the tag from a taggedreadof that file.→
registers must begin with @→
invalid text PUT body row at patch line 3; expected `+<text>` for content or bare `+` for a blank row, but the row does not begin with `+`→
"2:INLINE_TEXT" is not a valid positive line numberThe three forms produce three different diagnostics. None of them is accepted, and the whitespace-separated inline form is the one that returns a rule about registers rather than about body rows.
Positive control. The same scratch file, same session, with the body on the following line:
→ applied
PUT 2:followed by a+row on the next line also applied. AFT's description states that a plainNPUT replaces, so that form is supported here even though the reference implementation spells a single-line replacementPUT N.=N:. So the rejection above is about where the body starts, not about the text being unwritable through this tool.Observed in a real session (OpenCode
part.data,kimi-k3through an OpenAI-compatible proxy,edit_mode: "hashline"). Four calls returned this message. Their three distinct malformed rows were:(Payload contents are replaced with neutral samples; the shapes and line prefixes are unchanged.) Two of the four calls were a retry pair:
registers must begin with @registers must begin with @They are near-identical rather than byte-identical — the second is one character shorter — and both keep the inline row. That establishes one retry, not a loop; these records do not show why the shape repeated.
Frequency. kimi-k3
editcalls in the patch-mode surface, 2026-09-18 23:21 through 2026-09-19 10:44 — 13 calls in one session. Two separate ad-hoc runs of the same model, variant and config (scratch project, one Markdown edit each) are not part of this count:registers must begin with @(text on the address line)invalid text PUT body row at patch line N(row without a leading+)section tags must be exactly four hexadecimal digits(#LOCAL)addressed content no longer matches the … baselineTool execution aborted(tool-level abort, not a parser rejection)The five applied calls used
PUT <addr>:with+rows on the following lines, so the documented shape is reachable for this model. The two ad-hoc runs mentioned above produced valid patches on the first try. That shows the model can emit the documented shape; it does not show why other calls used a different one. The abbreviated error strings in the table are normalized categories, not verbatim full texts.Note on the format
This is not a request to accept the inline form. The section syntax, PUT/CUT operations,
+TEXTbody rows and colonless register paste are documented by@oh-my-pi/hashline,docs/tools/edit.mdat06aecdd5:PUT <N @name/PUT >N @name— paste a named register into a gapPUT N.=M @name/PUT N* @name— replace a range or block with a named registerAFT documents the same body-row rule and the same colonless paste form. Its variant also accepts a plain line number as a single-line replacement — measured above, not inferred. This comparison establishes shared documented syntax for those two rules; it does not establish implementation identity or full compatibility between the two.
PUT 15:<text>therefore has no valid reading in either variant: the:form takes rows on the following lines, the paste form has no colon. Rejecting it is correct. What is missing is the diagnostic, and a published reference for the format.A case-insensitive search of
docs/tools.mdat v0.56.2 found no occurrence ofhashline. The reference implementation publishes the body-row rule above as part of its own prompt text and tool documentation. The observed calls include both valid and invalid body shapes; these records do not establish why the invalid ones were emitted or repeated.Requests
+. Today the caller is told about a register rule; it needs the row rule. Rejecting the inline form itself is fine — that is this format's rule.docs/tools.mdat v0.56.2 contains no hashline section.:copies@name(or the anonymous register) and takes no body".readinstead of inventing one or copying an example value.Diagnostics
Relevant config (
~/.config/cortexkit/aft.jsonc):{ "tool_surface": "all", "edit_mode": "hashline" }Model:
kimi-k3via an OpenAI-compatible proxy (reasoning_effort: "max").Log output
Host-side record of one failing call, opened because the same payload holds a malformed inline row and a correctly shaped
PUT >98:++...row (OpenCodepart.data; payload text replaced with neutral samples):{ "type": "tool", "tool": "edit", "state": { "status": "error", "input": { "patch": "[example.txt#FCA9]\nPUT 85:| phase-a | sample change | worker | done | gate 2 |\nPUT >98:\n+sample text\n" }, "error": "registers must begin with @" } }