Agent-First Data v0.28.1: One Path, Many Nodes

by Agent-First Kit Contributors

Reading one field across a collection took three steps, and the middle one was sed against afdata's own output. A path may now contain `*`. Because `*` is a legal JSON key, the grammar gains `\*` to keep such documents addressable — which makes this a breaking change to path spelling.

The previous release added values, which reads many paths from a single parse of a document. It fixed the cost of asking which element has name X — 632ms to 17ms across a 110-package lockfile — but not the awkwardness. This is what the release pipeline’s own version checker looked like afterwards:

keys="$(afdata paths Cargo.lock package --input-format toml | sed 's/$/.name/')"
afdata values Cargo.lock $keys --input-format toml

Enumerate the children, append the field to each address with sed, then read them back. The middle step is string surgery on afdata’s own output — a tool’s user reaching around the tool to build addresses by hand. That code was written an hour after values shipped, by someone trying to use it idiomatically, which is about as clear a signal as the design is going to give.

A path can now say it directly:

$ afdata values Cargo.lock 'package.*.name' --input-format toml
agent-first-data
aho-corasick
android_system_properties
...

One call. The sed is gone.

The part that took the care

* is a legal key in JSON. Before this release, paths printed it and value read it, exactly like any other key. Reserving the bare form for patterns would have made every document carrying one unaddressable.

That is precisely the mistake this crate spent the last two releases undoing. The empty string is also a legal key, and rejecting it did not make it unreachable — only unspeakable, while paths went on emitting addresses that value refused. Repeating that with * would have been worse for knowing better.

So the grammar gains a third escape. \. embeds a dot, \\ embeds a backslash, and now \* embeds a star; join_path escapes stars on the way out. A document with a * key still round trips — paths just spells it \*, and that spelling reads back. Both can appear in the same path: *.\* expands the outer level and addresses a literal star at the inner one.

The rule that keeps it coherent is one spelling, one meaning. A bare * is refused by parse_path outright rather than being read as a key in value while expanding in values — a single spelling that means two things depending on which command you typed is how a grammar stops being a grammar.

Getting that wrong is easy, and the first attempt did: after parsing, \* and * are the same string, so the check that reserved the bare form rejected the escaped one too — re-stranding the very key it existed to protect. The parse now tracks which spelling was written.

Breaking, and worth saying plainly

This is a patch release that changes path spelling:

No document in this repository has a * key, so the measured impact across seven consumer tools is zero. But if you have one, escape it.

A patch number does not usually carry a change like this, and anyone pinning a caret range will receive it automatically. It is called out here rather than left to be discovered.