Agent-First Data v0.18: afdata Reads and Edits Your Config

by Agent-First Kit Contributors

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:

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 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

afdata used to describe your data and stop at the file’s edge. Now it edits the file too — carefully, in place, secrets closed.