Overview

The field name is the schema. Agents read latency_ms and know milliseconds, api_key_secret and know to redact — no external schema needed.

Agent-First Data (AFDATA) is a convention for self-describing structured data:

  1. Naming — Encode units and semantics in field name suffixes (_ms, _bytes, _secret, …)
  2. Output — Three formats (JSON/YAML/Plain) with automatic key stripping, value formatting, and secret redaction
  3. Protocol — Optional structured templates (ok, error, log) with trace for execution context
  4. Logging — AFDATA-compliant structured logging with span support (per-language integration)
  5. Channel discipline — machine-readable protocol/log events use stdout only; stderr is not a protocol channel

See the full specification and the agent skill for AI-assisted development.

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

A backup tool invoked from the CLI — flags, env vars, and config all use the same suffixes:

API_KEY_SECRET=sk-1234 cloudback --timeout-s 30 --max-file-size-bytes 10737418240 --log startup /data/backup.tar.gz

The tool reads env vars (UPPER_SNAKE_CASE), flags (--kebab-case), and config (snake_case) — all with AFDATA suffixes. When startup logging is enabled, it emits a startup log event. Three output formats, same data:

JSON (secrets redacted, original keys, for machines):

{"code":"log","event":"startup","args":{"input_path":"/data/backup.tar.gz"},"config":{"max_file_size_bytes":10737418240,"timeout_s":30},"env":{"API_KEY_SECRET":"***"}}

YAML (suffixes stripped from keys, values formatted, for humans):

---
code: "log"
event: "startup"
args:
  input_path: "/data/backup.tar.gz"
config:
  max_file_size: "10.0GB"
  timeout: "30s"
env:
  API_KEY: "***"

Plain (single-line logfmt, keys stripped, for log scanning):

args.input_path=/data/backup.tar.gz code=log event=startup config.max_file_size=10.0GB config.timeout=30s env.API_KEY=***

--timeout-stimeout_stimeout: 30s. API_KEY_SECRETAPI_KEY: "***". Same suffixes flow through env vars, CLI flags, JSON, and formatted output — the suffix is the schema.

CLI logging flags:

--log startup,request,progress,retry,redirect
--verbose   # shorthand for all log categories

API (15 functions + 2 types, same across all languages)

Function / TypeReturnsDescription
build_json_okJSON{code: "ok", result, trace?}
build_json_errorJSON{code: "error", error, hint?, trace?}
build_jsonJSON{code: "<custom>", ...fields, trace?}
redacted_valueJSONJSON-safe copy with default _secret redaction, for raw HTTP/MCP/SSE serializers
redacted_value_withJSONJSON-safe copy with explicit redaction policy
output_jsonStringSingle-line JSON, secrets redacted
output_json_withStringSingle-line JSON with explicit redaction policy
output_yamlStringMulti-line YAML, keys stripped, values formatted
output_plainStringSingle-line logfmt, keys stripped, values formatted
internal_redact_secretsvoidRedact _secret fields in-place
parse_sizeintParse "10M" → bytes; invalid/overflow returns language-specific invalid result
OutputFormattype"json" / "yaml" / "plain" enum/type
RedactionPolicytypeRedactionTraceOnly / RedactionNone / RedactionStrict
cli_parse_outputOutputFormatParse --output flag; error on unknown value
cli_parse_log_filtersString[]Normalize --log entries: trim, lowercase, dedup, remove empty
cli_outputStringDispatch to output_json / output_yaml / output_plain
build_cli_errorJSON{code:"error", error_code:"invalid_request", hint?, retryable:false, trace:{duration_ms:0}}

AFDATA suffixes describe local field semantics; they are not a full schema language. Use JSON Schema, OpenAPI, database constraints, or typed APIs for required fields, enums, ranges, and object shapes. For raw JSON transports that do not call output_json (HTTP bodies, MCP tool returns, SSE events), call redacted_value first.

AFDATA Logging

AFDATA-compliant structured logging. Log output is formatted using the library’s own output_json/output_plain/output_yaml functions — same suffix processing, key stripping, and secret redaction as the core output API. Span fields are automatically flattened into each event line, solving concurrent request interleaving.

Each language integrates with its native logging ecosystem:

LanguageIntegrationSpan MechanismOutput Formats
Rusttracing Layer (feature "tracing")tracing spansinit_json / init_plain / init_yaml
Golog/slog HandlerWithAttrs / WithSpan(ctx)InitJson / InitPlain / InitYaml
Pythonlogging Handlercontextvarsinit_logging_json / init_logging_plain / init_logging_yaml
TypeScriptBuilt-in loggerAsyncLocalStorageinitJson / initPlain / initYaml

Minimum envelope contract across languages:

JSON output (production — secrets redacted, original keys):

{"timestamp_epoch_ms":1739000000000,"message":"Processing","request_id":"abc-123","code":"info"}

Plain output (development — keys stripped, values formatted):

code=info message=Processing request_id=abc-123 timestamp_epoch_ms=1739000000000

Rust:

use agent_first_data::afdata_tracing;
afdata_tracing::init_json(EnvFilter::new("info"));   // or init_plain / init_yaml

let span = info_span!("request", request_id = %uuid);
let _guard = span.enter();
info!("Processing");

Go:

afdata.InitJson()   // or InitPlain / InitYaml

ctx := afdata.WithSpan(ctx, map[string]any{"request_id": uuid})
afdata.LoggerFromContext(ctx).Info("Processing")

Python:

from agent_first_data import init_logging_json, span  # or init_logging_plain / init_logging_yaml

init_logging_json("INFO")
with span(request_id=uuid):
    logger.info("Processing")

TypeScript:

import { log, span, initJson } from "agent-first-data";  // or initPlain / initYaml

await span({ request_id: uuid }, async () => {
  log.info("Processing");
});

Supported Suffixes

CategorySuffixesExample
Duration_ns, _us, _ms, _s, _minutes, _hours, _dayslatency_ms: 1280latency: 1.28s
Timestamps_epoch_ns, _epoch_ms, _epoch_s, _rfc3339created_at_epoch_ms: 1738886400000created_at: 2025-02-07T00:00:00.000Z
Size_bytes (output), _size (config input)file_size_bytes: 5242880file_size: 5.0MB
Currency_msats, _sats, _btc, _usd_cents, _eur_cents, _jpy, _{code}_centsprice_usd_cents: 999price: $9.99
Other_percent, _secretcpu_percent: 85cpu: 85%

Language Documentation

License

MIT