Overview

The field name is the schema. Agents read latency_ms and know milliseconds, api_key_secret and know to redact, and callback_url and know URL credentials must be scrubbed.

Agent-First Data (AFDATA) is a convention plus four small libraries:

  1. Naming - encode units and sensitivity in field names (_ms, _bytes, _secret, _url, …)
  2. Output - render the same data as JSON, YAML, or plain logfmt with deterministic formatting
  3. Protocol - optional JSONL objects with code, result/error, and trace
  4. Logging - structured logs that use the same redaction and suffix formatting rules
  5. Channel discipline - machine-readable events go to stdout; stderr is not a protocol stream
  6. Stream redirection - optional CLI helper to send stdout and stderr to separate files without changing their formats

See the full specification and the agent skill.

Installation

cargo add agent-first-data        # Rust
pip install agent-first-data      # Python
npm install agent-first-data      # TypeScript
go get github.com/agentfirstkit/agent-first-data/go  # Go

Quick Example

Input data:

{"code":"log","event":"startup","args":{"timeout_s":30,"api_key_secret":"sk-123"},"db_url":"postgres://user:p@ss@db/app?token_secret=abc","trace":{"duration_ms":1280}}

JSON keeps original keys and raw values, but redacts secrets:

{"code":"log","event":"startup","args":{"timeout_s":30,"api_key_secret":"***"},"db_url":"postgres://user:***@db/app?token_secret=***","trace":{"duration_ms":1280}}

YAML and plain strip formatting suffixes and format values:

---
args:
  api_key: "***"
  timeout: "30s"
code: "log"
db_url: "postgres://user:***@db/app?token_secret=***"
event: "startup"
trace:
  duration: "1.28s"
args.api_key=*** args.timeout=30s code=log db_url=postgres://user:***@db/app?token_secret=*** event=startup trace.duration=1.28s

Current API Surface

Language names follow each ecosystem’s casing. The shared contract is:

GroupAPIs
Protocol buildersbuild_json_ok, build_json_error, build_json
Outputoutput_json, output_json_with, output_json_with_options, output_yaml, output_yaml_with_options, output_plain, output_plain_with_options
Redactionredacted_value, redacted_value_with, redacted_value_with_options, redact_secrets_in_place, redact_secrets_in_place_with_options
URL redactionredact_url_secrets, redact_url_secrets_with_options
CLI helpersparse_size, normalize_utc_offset, is_valid_rfc3339_date, is_valid_rfc3339_time, cli_parse_output, cli_parse_log_filters, cli_output, cli_output_with_options, build_cli_error, build_cli_version, cli_handle_version_or_continue
TypesOutputFormat, VersionConfig (Rust), RedactionPolicy, RedactionOptions, OutputStyle, OutputOptions

RedactionPolicy has two explicit overrides: RedactionTraceOnly and RedactionNone. The default policy is full redaction: every _secret or configured secret-name field is replaced by ***, including object and array subtrees. _url fields scrub userinfo passwords and secret-named query parameters; surrounding whitespace is trimmed, and internal whitespace causes the whole URL field to become ***.

build_cli_error(message, hint?) returns only the protocol error shape: {code:"error", error: message, hint?}. It does not invent retry metadata or fake traces.

Version helpers should run before the app parser so bare --version stays conventional and --version --output json|yaml|plain emits a structured {code:"version", version} event instead of being intercepted by parser built-ins.

The Rust cli-help and skill-admin features are implementation utilities for spore binaries. They are intentionally separate from the cross-language AFDATA formatting contract; language README files point back here instead of duplicating the full reference.

Optional stream redirection uses canonical CLI names:

--stdout-file <PATH>
--stderr-file <PATH>

When enabled, stdout bytes are appended to the stdout file and stderr bytes are appended to the stderr file. This is a stream destination override, not a second protocol stream: stdout keeps the selected AFDATA format, and stderr keeps native diagnostics such as Rust panics, Python tracebacks, or runtime errors. Rotation is left to external tooling.

Logging Contract

Logging integrations emit structured records through the same output formatters.

Required log fields:

code is always "log"; the logging level lives in level. This prevents an error-level log line from being mistaken for a terminal protocol result with code:"error".

Example plain line:

code=log level=info message=Processing request_id=abc-123 timestamp=2025-02-08T07:33:20.000Z

Name secret log fields explicitly (api_key_secret, db_url) so redaction can see the field name. URL fields should end in _url; any token-bearing query parameter must either be renamed to an _secret parameter such as token_secret, or listed in secret_names / SecretNames / secretNames when legacy names cannot change. Do not log a whole secret-bearing object as a pre-rendered debug string.

Supported Suffixes

CategorySuffixes
Duration_ns, _us, _ms, _s, _minutes, _hours, _days
Timestamps_epoch_ns, _epoch_ms, _epoch_s, _rfc3339
Size_bytes for numeric output, _size for config input strings
Currency_msats, _sats, _btc, _usd_cents, _eur_cents, _jpy, _{code}_cents where code is 3-4 ASCII letters
Strict strings_bcp47, _utc_offset, _rfc3339_date, _rfc3339_time
Other_percent, _secret, _url

YAML and plain output sort keys by UTF-16 code unit order after key stripping. Plain output escapes both keys and values so every record stays one physical line.

Language Documentation