agent-first-data 0.36.1

A naming convention that lets AI agents understand your data without being told what it means, plus the afdata CLI and a Rust library for reading Markdown structure and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.
Documentation
# Agent-First Data for Rust

```bash
cargo add agent-first-data
# data/protocol surface only — no CLI compiler, emitter, tracing, or skill admin:
cargo add agent-first-data --no-default-features
```

```rust
use agent_first_data::{OutputFormat, OutputOptions, json_result, render};
use serde_json::json;

fn main() {
    let event = json_result(json!({
        "api_key_secret": "sk-123",
        "latency_ms": 1280,
        "db_url": "postgres://user:p@ss@db/app?token_secret=abc"
    }))
    .build();

    let options = OutputOptions::default();
    println!("{}", render(event.as_value(), OutputFormat::Json, &options));
    println!("{}", render(event.as_value(), OutputFormat::Plain, &options));
}
```

Useful names use Rust casing: `render` (the single
`value × format × options → String` entry point), `OutputFormat`,
`OutputOptions`, `Redactor`, `redacted_value`, `redact_url_secrets`,
`redact_urls_in_text`, `redact_argv`, `normalize_utc_offset`, `is_valid_rfc3339_date`,
`is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `CliSpec`,
`CommandSpec`, `ArgSpec`, `Combination`, `OutputSpec`, `SourceSet`,
`SourceScheme`, `ValueSource`, `ErrorSpec`, `lint_value`,
`build_afdata_cli`, and `decode_protocol_event`.

The general-purpose features below are on by default; `--no-default-features`
opts out. Tracing integration is behind `tracing`:
`afdata_tracing::AfdataLayer` is composable and accepts an injected writer,
while `try_init` is the global-subscriber convenience entry point. The whole
CLI surface — `CliSpec`, help-v2, emitters, and the AFDATA adapter — is behind
`cli`; skill administration is behind `skill-admin`; stdout/stderr file
redirection is behind `stream-redirect`. Unix hardened document opens are behind
`libc`, which is on by default and is also enabled by `stream-redirect`.
`render` and `OutputFormat` stay available with every feature off.

```rust
use agent_first_data::afdata_tracing::{self, LogFormat};
use agent_first_data::Redactor;
use tracing_subscriber::EnvFilter;

fn init_logging() -> Result<(), tracing_subscriber::util::TryInitError> {
    afdata_tracing::try_init(
        EnvFilter::new("info"),
        LogFormat::Json,
        Redactor::new().secret_names(["authorization"]),
    )
}
```

For a closed-world CLI, bind exact action handlers and resolve argv to
`BoundOutcome<R>`. Execute its `BoundOutcome::Run(invocation)` branch with
`invocation.run()`; the handler is already attached to that invocation. An
unbound `BuiltCliSpec::resolve_from` still returns `CliOutcome`. The application
owns the process lifecycle because a finite command, a long-running event
stream, and raw-byte output need different
execution and output policies. Use the resolved `OutputPlan` with
`CliEmitter::finish`, `write_raw`, and, when enabled, `stream_redirect`.
[`rust/examples/agent_cli.rs`](examples/agent_cli.rs) shows the finite-command
case.

An `ArgSpec` can declare indirect values with
`sources(SourceSet::config())` (`env:NAME`, `file:PATH[#DOT_PATH]`, or
`file+FORMAT:PATH#DOT_PATH`) or `SourceSet::stream()` (also `stdin`, `fd:N`,
and `prompt`). Parse the resolved string with that same set, then call
`ValueSource::read()` for an ordinary value or `read_secret()` for a
credential. The latter returns
`agent_first_data::value_source::SecretString`; its `Debug` and `Display`
render `***`, and only `expose_secret()` reveals it at the boundary that needs
the credential. Host-only schemes are declared with `host_scheme` and read by
the host. Empty strings remain values, reads are capped, and an `fd:N` read
does not close the caller-owned descriptor.

Canonical flags are `--stdout-file` and `--stderr-file`. They redirect the corresponding stream to an append-only file; stdout keeps the selected AFDATA output format, and stderr keeps native diagnostics such as panics and backtraces.

## Behavior Notes

- Default redaction replaces every `_secret` or configured secret-name subtree with `***`, including objects and arrays.
- `_url` fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed, and internal whitespace or a `://` that is not the start of a clean URL redacts the whole field.
- `Redactor::url_names` applies that URL treatment to exact legacy field names
  and recurses through collections. `redact_urls_in_text` is the explicit
  scheme-URL-only prose helper; ordinary structured redaction never scans prose.
- `Redactor::reveal_names` reveals exact structured field names at the selected output boundary. Children still pass through redaction; URL parameters and argv receive no exemption.
- YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
- Logging records use `kind:"log"` with a nested `log` payload and a separate `level` field, so error-level logs are not terminal protocol errors.
- Use `AfdataLayer::new(...).with_writer(...)` when composing tracing or
  retaining a `StructuredLogHandle`; use `try_init` when a global subscriber is
  the desired convenience. Tracing `TRACE` maps to AFDATA `debug`; adapter-owned
  `level`, `message`, and `timestamp_epoch_ms` cannot be overridden by span or
  event fields.
- Build new CLIs from one `CliSpec`. Register each command-local argument and
  every legal `Combination`, call `build()`, bind exact `action_id` handlers,
  and resolve to `BoundOutcome<R>`. Parsing, typed values, output planning, and
  help-v2 come from the registry; the application retains its command lifetime,
  redaction options, emission strategy, and exit policy.
- `ResolvedInvocation::required` returns `&CliValue` for a required or fixed
  argument; its typed accessors return `Option`. The bound run branch owns its
  handler, so there is no separate dispatch step that can accept an invocation
  from another registry. `BoundCliSpec::call_every_combination()` checks handler
  argument ids with synthetic invocations, but actually runs every handler:
  use it only for pure projections from argv to command values, before carrying
  out those commands.
- `CliError` carries a closed rule plus caller-safe `message` and `hint` —
  never raw argument values. `cli_error_event` renders it as a strict protocol
  event; `exit_code()` is the process status.
- `synthetic_invocations()` generates type-correct argv for every combination
  and every fixed `one_of` member, so the same registered shapes can be
  round-trip tested independently of help placeholders.
- Use `document::DocumentFile::open_capped` for bounded untrusted reads,
  `create_atomic` for a parsed first commit, `decode` for a typed read, and
  `edit_and_validate` for a typed transactional edit. TOML collections are source-preserving except
  arrays of tables, which require an explicit identity and are refused.
- Feature `libc` gives `open_capped` nonblocking special-file rejection and
  atomic `SymlinkPolicy::NoFollow` on unix. Without it, `NoFollow` is
  unsupported and opening a special file may block before its type is checked.
- `ErrorSpec` / `ErrorCatalog` keep stable public errors separate from runtime
  diagnostics. `lint_value`, strict-event assertions, and redaction-canary
  assertions test final serialized boundaries in-process. Canary assertions
  inspect successive decoding layers, including JSON escapes around
  percent-encoded nested URLs.
- Protocol payloads are `serde_json::Value` built with `arbitrary_precision`,
  so decode and render keep every digit. Cargo unifies that feature across the
  whole dependency graph, including builds with default features disabled:
  convert at another serializer's typed boundary, or it may render
  `serde_json`'s private number marker.
- `stream-redirect` enables `libc` and provides Unix fd-level redirection where supported. It is stream destination control, not a second AFDATA protocol stream, and it does not implement rotation.

A file value source without `#DOT_PATH` reads the complete UTF-8 file verbatim,
without document parsing or scalar conversion. An explicit `#` requires a
non-empty document address. Whole files and stream sources (`stdin`, `fd:N`)
read through `read()` are capped at 16 MiB and preserve trailing whitespace;
credential streams through `read_secret()` and terminal prompts retain their
1 MiB bound. File sources require regular files. Addressed file sources retain
their typed scalar semantics.

## Reference

- Formal cross-language contract: [spec/agent-first-data.md]https://github.com/agentfirstkit/agent-first-data/blob/main/spec/agent-first-data.md
- Conformance fixtures: [spec/fixtures]https://github.com/agentfirstkit/agent-first-data/tree/main/spec/fixtures
- Agent skill: [skills/agent-first-data/SKILL.md]https://github.com/agentfirstkit/agent-first-data/blob/main/skills/agent-first-data/SKILL.md