Agent-First Data v0.18: afdata Reads and Edits Your Config
v0.18 gives afdata a document CLI and library: read and safely edit JSON, TOML, YAML, dotenv, and INI files by dot-path — show/get/value to read, set/unset/add/remove to edit — with source-preserving atomic writes, symlink/hardlink guards, and secrets that stay redacted even on a direct read.
Every AFDATA verb used to point one way. A field name tells an agent what a value
means; redaction hides the secrets inside it; formatting renders it for a human;
validation checks the envelope around it. Data comes in, a well-shaped record
goes out. The one thing afdata couldn’t do was change a value and hand the file
back.
v0.18 adds the other half. afdata now reads and safely edits structured
config and data files — JSON, TOML, YAML, dotenv, and INI — addressed by
dot-path. It’s the same tool whether a spore embeds the library for generic
config access without a per-field dispatch table, or a shell script wants one
dependency for a one-off read and edit.
It’s a breaking release — AFDATA is still pre-1.0 and carries no compatibility layer.
Three ways to read
afdata show config.toml # the whole document, as one AFDATA record
afdata get server.port config.toml # one value, as an AFDATA record (secrets redacted)
host=$(afdata value server.host config.toml) # the raw scalar, for shell substitution
The three are not redundant — they’re the three shapes an agent actually needs.
show is the whole document as a single record it can reason over. get pulls
one field but keeps it a record — suffix-formatted, secrets still starred out —
so a get is safe to log or pass on. value is the bare scalar with nothing
around it, for the moment you just want $host in a shell. Ask value for an
object or array and it refuses (path <KEY> is not a scalar) rather than dumping
a JSON blob into a variable — reach for get or show when you want a subtree.
Editing that doesn’t rewrite the file
afdata set server.port 8080 --input-file config.toml
afdata unset server.tls.legacy --input-file config.toml
set and unset work on all five formats; add and remove operate on a keyed
list and, for now, are implemented for JSON and YAML (TOML/dotenv/INI answer with
a structured “not implemented” instead of guessing).
The important part is what an edit doesn’t touch. set changes one scalar and
leaves everything else byte-identical — comments, key order, blank lines, the
quoting style of the neighbors. That’s the difference between an edit and a
parse-then-reserialize round trip, which reflows a hand-maintained file into a
tool’s idea of tidy and buries your one-line change in a hundred-line diff. For
an agent editing a file a human also maintains, a clean diff is the difference
between a change that gets reviewed and one that gets reverted.
Three more rules make the write path safe by default:
- Atomic, and no writing through links. A failed write leaves the original untouched, and the CLI refuses to write through a symlink or hardlink rather than clobber whatever is on the other end.
- One dot-path grammar, no guessing. A literal dot in a key is
\.and a literal backslash is\\; an unrecognized escape is an error, never a silently accepted guess.add/removerequire an explicit--slug-field, so “which entry” is always stated. - One input model. Reads take a positional file or piped stdin (defaulting to
JSON, and rejecting an interactive TTY instead of hanging on a prompt);
mutations require
--input-file. Format is inferred from the extension and overridable with--input-format.
Secrets stay closed on the way out
Reading a config is exactly where a credential leaks. So a _secret leaf — or a
name you pass with --secret-name — stays redacted even when you get it
directly by path. A targeted read can’t be the hole the whole convention was
built to close. Revealing one is the explicit, auditable opt-in:
afdata get db.password_secret config.toml # -> ***
afdata value db.password_secret --reveal-secret config.toml # -> the actual value
Same rule as everywhere else in AFDATA — the field name carries the meaning, and
_secret means “don’t show this unless someone asks out loud.”
Why this is the half that was missing
A config file is the one kind of structured data an agent both reads and
writes, and the write is where the damage lives: reflow the whole file, drop a
comment, clobber an unrelated key, follow a symlink out of the sandbox, leak a
secret into the diff. Until v0.18, an agent that needed to change one value in a
config.toml had to parse it, mutate it, and serialize it back — losing the
formatting and taking on every one of those risks itself. Now it’s one command
that makes the safe thing the default.
The Rust library lives at agent_first_data::document (Document /
DocumentFile plus the format backends, gated by the toml / yaml / dotenv /
ini features; JSON is core). The verbs there take typed document::Values, not
CLI strings — so a Rust caller holding a real value gets the same
source-preserving, atomic write path the CLI does.
The rest of 0.18
The release also runs an honesty pass over the four-language library API — worth a line here, detailed in the release notes:
- the eight output functions collapse into a single
render(value, format, options); - validation returns a typed
ProtocolViolation(stable rule slug + JSON pointer) instead of a string, and the stream validator reports every violation; - the redaction policy gets a name (
RedactionPolicy::All, the safe default); - version/help collapse to one blessed protocol-v1 behavior;
- and the result/progress/log builders’
build()stop returning aResultthey can never fail.
The formatting command you already knew was renamed for what it does: afdata format is now afdata render. Editing is the new verb; rendering is the old
one wearing an honest name.
Upgrading
- Editing config? Reach for
afdata set/unset(any format) andadd/remove(JSON or YAML) instead of a parse-edit-serialize script — you get source-preserving, atomic writes and the symlink guard for free. - Rename
format→renderat every CLI call site. - Reading a scalar into a shell var is
afdata value <KEY> <FILE>(key first, then file); useget/showfor a record or a subtree. - Library callers: the API-cleanup renames (
render,ProtocolViolation,RedactionPolicy::All/PlainStyle,Document::parse) are listed in the release notes.
afdata used to describe your data and stop at the file’s edge. Now it edits the
file too — carefully, in place, secrets closed.