This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Tutorials

Start-to-finish ajq walkthroughs.

Run these in order. The second tutorial uses the setup from the first.

Available tutorials

  1. Your first ajq pipeline — install nothing, run a real jq query through ajq, and see byte-for-byte deterministic output.
  2. See how ajq plans a semantic query — write your first fuzzy predicate and use --explain to watch ajq split it into a deterministic part and a semantic part.

For task-focused instructions, see the how-to guides.

1 - Your first ajq pipeline

Run a real jq query through ajq and see deterministic, byte-for-byte output.

Run a real jq query through ajq. You’ll process a small JSON document, filter it, and confirm ajq matches jq for ordinary queries. No model, API key, or configuration.

Time: 5 minutes. Requires ajq on your PATH.

Step 1 — Get ajq

Use the checksum-verifying install script, a manual release download, or build from source (see Install ajq for details):

curl -fsSL https://raw.githubusercontent.com/ricardocabral/ajq/main/scripts/install.sh | sh

Check that it works:

ajq --version

This tutorial uses only deterministic jq, so there’s nothing else to set up — no model download, no API key.

Step 2 — Run a query

ajq reads JSON from standard input and applies the query you pass as its argument. Let’s pipe a small document in and pull out one field:

printf '{"user":{"name":"Ada","active":true}}' | ajq '.user.name'

Output:

"Ada"

ajq quotes the string exactly as jq would. It uses gojq under the hood.

Step 3 — Filter a list

Now something more interesting. Here’s a document with a list of users; we want the names of the active ones:

printf '{"users":[{"name":"Ada","active":true},{"name":"Grace","active":false}]}' \
  | ajq -r '.users[] | select(.active) | .name'

Output:

Ada

Two things happened:

  • .users[] | select(.active) | .name is an ordinary jq pipeline — iterate the users, keep the active ones, project the name.
  • The -r flag asked for raw output, so the string came out as Ada without quotes.

Step 4 — Prove it’s deterministic

Run the exact same command again:

printf '{"users":[{"name":"Ada","active":true},{"name":"Grace","active":false}]}' \
  | ajq -r '.users[] | select(.active) | .name'

You get Ada again, byte for byte. Ordinary jq parts are byte-reproducible. Semantic operators, added later, are the only parts that touch a model; everything else stays deterministic. You’ll see that in the next tutorial.

Result

  • ajq runs real jq queries and produces jq-identical output.
  • -r emits raw strings; without it, ajq emits JSON.
  • Ordinary pipelines are deterministic and reproducible.

Next

2 - See how ajq plans a semantic query

Write your first fuzzy predicate and watch ajq split it into deterministic and semantic parts with –explain.

Write a fuzzy match and inspect the plan. --explain does not contact a model, so you can inspect and cost any query before running it for real.

Requires ajq on your PATH (see Install ajq).

Step 1 — Write a fuzzy predicate

Ordinary jq can test text with test() or ==, but only literally. ajq adds a fuzzy operator, =~, that asks a model “does this value match this description?”. Here’s a stream of three messages, and a query that keeps the ones that read as urgent:

printf '[{"msg":"urgent"},{"msg":"urgent"},{"msg":"other"}]' \
  | ajq --explain '.[] | select(.msg =~ "urgent") | .msg'

--explain prints the plan instead of running the query.

Step 2 — Read the plan

You’ll see output like this:

ajq explain v1
query: ".[] | select(sem_match(.msg; \"urgent\")) | .msg"
execution: semantic split plan
deterministic: no
model_calls: input-dependent
backend_calls: input-dependent
byte_reproducible: cache-dependent
stdin: harvested for estimates
planned_call_sites: 1
semantic_predicates: 1
semantic_values: 0
estimates:
  estimate_status: available
  static_call_sites: 1
  input_frames: 1
  harvested_judgements: 3
  post_dedup_judgements: 2
  mock_judge_batches: 1
  over_harvest_bound: post_dedup_judgements == mock distinct judgements; may be a safe superset of execute-needed judgements
subgraphs:
  deterministic: jq outside semantic call sites
  semantic: 1 planned call site(s)
semantic_plan:
  - call_id: 1
    op: "sem_match"
    kind: "predicate"
    value_expr: ".msg"
    specs: ["urgent"]
    source_range: 13:38
    gated: n/a
    execution: 3-phase
    subgraph: semantic

Important lines:

The query was rewritten

query: ".[] | select(sem_match(.msg; \"urgent\")) | .msg"

You wrote .msg =~ "urgent", but the plan shows sem_match(.msg; "urgent"). The =~ operator is surface sugar: ajq desugars it into a plain jq function call before planning. Everything semantic is ultimately a sem_* function.

ajq found the semantic node

semantic_plan:
  - call_id: 1
    op: "sem_match"
    kind: "predicate"
    value_expr: ".msg"
    specs: ["urgent"]

There is exactly one semantic call site — the sem_match on .msg. Everything else in your query (.[], select, .msg projection) is deterministic jq. ajq has split the pipeline into a deterministic majority and a single fuzzy decision.

Deduplication is already visible

harvested_judgements: 3
post_dedup_judgements: 2

Your input had three items, but two of them have the same .msg value ("urgent"). ajq would collect 3 values, deduplicate them to 2 distinct ones, and only ever ask the model about those 2. Identical values cost one decision — this is how ajq keeps cost tied to the number of distinct fuzzy decisions, not the size of your input.

Step 3 — Change the input and watch the estimate move

Add another distinct message and re-run:

printf '[{"msg":"urgent"},{"msg":"urgent"},{"msg":"other"},{"msg":"ping the on-call"}]' \
  | ajq --explain '.[] | select(.msg =~ "urgent") | .msg'

Now harvested_judgements becomes 4 and post_dedup_judgements becomes 3: one new distinct value to judge.

Result

  • =~ is fuzzy matching; ajq desugars it to sem_match(...).
  • --explain shows the plan without contacting a model — safe to run anytime.
  • ajq splits every query into a deterministic part and semantic call sites.
  • Deduplication means identical values are judged once.

Running it for real

Drop the --explain flag and select the deterministic mock backend so the tutorial stays network-free:

printf '[{"msg":"urgent"},{"msg":"urgent"},{"msg":"other"}]' \
  | ajq --backend mock '.[] | select(.msg =~ "urgent") | .msg'

Output:

"urgent"
"urgent"

Use --backend local or a cloud backend after following the matching how-to guide. --explain stays the way to inspect and budget a query before you spend real model calls.

Next