Skip to content

[FIX] skills: file-write results are read back from disk (add write-file-b64)#243

Open
MartinEbner wants to merge 1 commit into
asi-alliance:mainfrom
MartinEbner:feature/verified-file-writes
Open

[FIX] skills: file-write results are read back from disk (add write-file-b64)#243
MartinEbner wants to merge 1 commit into
asi-alliance:mainfrom
MartinEbner:feature/verified-file-writes

Conversation

@MartinEbner

@MartinEbner MartinEbner commented Jul 11, 2026

Copy link
Copy Markdown

write-file and append-file return a static success atom (WRITE-FILE-SUCCESS) that certifies
only that the Prolog open/write/close calls succeeded — not that the intended content is on
disk. Two failure classes pass straight through it, both extensively documented by users running
agents on this stack: content mangled on the way to disk (nested quotes, backslashes) while the
write itself "succeeds", and the agent later claiming writes or byte counts with nothing in its
context to check against. "Read the file back after every write" as a prompt rule has repeatedly
proven unenforceable — the model skips it, and when asked to confirm it invents plausible numbers.
The check has to live in the harness, where it cannot be skipped.

This PR makes the skill result the verification. The file-writing skills route through a new
src/fileio.py (same py-call pattern as websearch/logger) and return a result built
exclusively from a read-back of the file on disk:

WRITE-VERIFIED file=/tmp/x.txt bytes=201 sha256=e02bd6c2bec42055 head='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' tail='BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'
APPEND-VERIFIED file=/tmp/log.txt bytes=12 sha256=2751a3a2f303ad21 head='line1\nline2\n' tail=''
WRITE-FAILED file=/nope/x.txt: [Errno 2] No such file or directory: '/nope/x.txt'
APPEND-FAILED file=/tmp/absent.txt: file does not exist

(real output of the implementation, not composed for the PR)

These values come from disk, not from the model — the agent relays them and can spot a mangled
write in the same cycle by comparing head/tail against what it intended. Failures return
explicit *-FAILED strings instead of raising into the loop (today a failed open surfaces as a
generic multi-command error with no file detail).

A new skill write-file-b64 takes base64-encoded content and writes the decoded bytes —
closing the quote/backslash-mangling class at the input side entirely (binary-safe; whitespace
inside the argument is tolerated, which future-proofs for #223's multi-line parsing — today's
line-based parser requires the argument on a single line, and the skill description says so).
The verified result then proves the round trip. Registered in helper.py command parsing and
lib_omegaclaw.metta like the other py-call modules; docs/reference-skills-io.md updated
(the Returns sections there said True, which was already out of date) with a write-file-b64
entry added.

Design notes:

  • append-file keeps its contract: the file must exist, a trailing newline is added. Its bytes=
    reports the file size after the append (read-back ground truth), not the appended chunk size.
    On a missing file the result changes from a silent Prolog goal failure (no result at all) to
    the explicit APPEND-FAILED file=…: file does not exist.
  • appendToHistory (memory.metta) keeps the Prolog-backed append under the name
    append-file-raw — it passes a (library …) file-spec term that only the Prolog open/4
    path resolves. After the rebase onto the merged OMEGA-239 (safe history/prompt reading), its
    exists-check + create collapses into the single append-file-raw call: append-file-raw
    drops the old body's leading exists_file guard, so open/4 in append mode creates a
    missing history file and the history path still self-heals (verified in PeTTa; exercised by
    mock/test_memory_missing_history_file_mock.py in run_mandatory). OMEGA-239's
    getPrompt/getHistory safe-reads are untouched.
  • Nothing consumes the old WRITE-FILE-SUCCESS/APPEND-FILE-SUCCESS atoms (definition sites
    only), and string results are already the norm for skills (read-file, shell, metta,
    websearch).
  • Result strings are single-line: head/tail snippets are 80 bytes with backslashes, newlines
    and carriage returns escaped (\\, \n, \r) — a literal backslash-n in content stays
    distinguishable from a real newline; sha256 is truncated to 16 hex chars and skipped for
    files over 2 MB. For files of 81–160 bytes the tail is omitted (head plus hash cover
    verification).
  • Edge case, accepted: if the write lands but the read-back itself fails (file deleted or
    permissions changed in the same instant), the result is *-FAILED although bytes are on disk —
    erring on the side of the agent re-checking rather than trusting.
  • write-file writes the exact bytes it was given (no implicit trailing newline).

Tests: Autotests/unit/test_fileio_verified_writes.py — 11 in-process unit tests (module
loaded by file path; no container, network, or token), wired into Autotests/run_mandatory:
disk read-back correctness (bytes/sha256/head/tail), quotes-and-backslashes land byte-exact,
UTF-8 byte counting, long-content snippets, a literal backslash-n stays distinguishable from a
real newline in snippets, append adds the newline and verifies the whole file, write-failure and
append-to-missing-file return explicit failure strings, b64 round-trip of hostile content,
line-wrapped b64 accepted, invalid b64 rejected without writing. The existing mock/ container
tests (test_create_file_mock etc.) exercise the rewired skills end-to-end.

Run: pytest --confcutdir=Autotests/unit Autotests/unit/ (the --confcutdir skips the
container-session fixture in the Autotests root conftest; CI runs the file via run_mandatory).

Related open PRs:

(This branch is rebased onto current main, which already includes the merged #222 plugin API
and OMEGA-239 — both merged cleanly.)

@vsbogd vsbogd changed the title skills: file-write results are read back from disk (add write-file-b64) [FIX] skills: file-write results are read back from disk (add write-file-b64) Jul 16, 2026
@vsbogd vsbogd added the fix label Jul 16, 2026
write-file and append-file returned a static success atom
(WRITE-FILE-SUCCESS) that certifies only that the Prolog open/write/close
calls succeeded - not that the intended content is on disk. Two documented
failure classes pass straight through it: content mangled on the way to
disk (nested quotes, backslashes) while the write itself "succeeds", and
the agent later claiming writes or byte counts with nothing in its context
to check against. Read-back-after-write discipline has proven unenforceable
by prompting alone.

The file-writing skills now route through a new src/fileio.py (same py-call
pattern as websearch/logger) and return a result built exclusively from a
read-back of the file on disk:

  WRITE-VERIFIED file=/tmp/x.txt bytes=201 sha256=1f9a2b... head='...' tail='...'
  APPEND-VERIFIED file=/tmp/log.txt bytes=12 sha256=... head='line1\nline2\n' tail=''
  WRITE-FAILED file=/nope/x.txt: [Errno 2] No such file or directory
  APPEND-FAILED file=/tmp/absent.txt: file does not exist

The agent can only relay these values, not compose them, and can spot a
mangled write in the same cycle by comparing head/tail against what it
intended. Failures return explicit *-FAILED strings; today a failed open
surfaces as a generic multi-command error with no file detail.

New skill write-file-b64 takes base64-encoded content and writes the
decoded bytes, closing the quote/backslash-mangling class at the input
side (binary-safe, tolerates line-wrapped base64; registered in helper.py
command parsing and lib_omegaclaw.metta like the other py-call modules).

Design notes:
- append-file keeps its contract: the file must exist, a newline is added.
- appendToHistory (memory.metta) keeps the Prolog-backed append under the
  name append-file-raw: it passes a (library ...) file-spec term that only
  the Prolog open/4 path resolves. Its body drops the old exists_file
guard (rebase note below).
- Nothing consumes the WRITE-FILE-SUCCESS/APPEND-FILE-SUCCESS atoms
  (definition sites only), and string results are already the norm for
  skills (read-file, shell, metta, websearch return strings).
- Result strings are single-line: head/tail snippets are 80 bytes with
  backslashes, newlines and carriage returns escaped, so a literal
  backslash-n in content is distinguishable from a real newline; sha256
  is truncated to 16 hex chars and skipped for files over 2 MB.

Docs: reference-skills-io.md Returns sections updated (they said "True",
which was already out of date) and a write-file-b64 entry added; README
reference line extended.

Tests: Autotests/unit/test_fileio_verified_writes.py - 11 in-process unit
tests (module loaded by file path; no container, network, or token), wired
into Autotests/run_mandatory. The existing mock/ container tests
(test_create_file_mock etc.) exercise the rewired skills end-to-end.

Rebased onto main after OMEGA-239 (safe history/prompt reading) merged:
its appendToHistory exists-check + create collapses into a single
append-file-raw call, with append-file-raw dropping the old body's
leading exists_file guard so that SWI open/4 in append mode creates a
missing history file (verified in PeTTa: with the guard the clause
fails silently on a missing file; without it the file is created and
appended). Net behavior on the harness history path matches main,
including the recreate scenario of
mock/test_memory_missing_history_file_mock.py. OMEGA-239's
getPrompt/getHistory safe-reads are untouched.
@MartinEbner
MartinEbner force-pushed the feature/verified-file-writes branch from be47a42 to b1598ba Compare July 17, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug 9: append-file Concatenates Without Newline After write-file

2 participants