Skip to content

hashline edit: inline PUT body reports a register error instead of the body-row rule #324

Description

@ybw0014

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

  1. 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.
  2. 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.
  3. 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".
  4. 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):

{
  "tool_surface": "all",
  "edit_mode": "hashline"
}

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 @" } }

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions