Architecture
ajq is a small pipeline of components.
Data flow
stdin → [Reader/Framer] → [Desugar] → [gojq.Parse] → [Planner] ──→ [Explain]
│
[3-phase Executor]
├─ gojq (deterministic)
└─ Semantic Executor → [Backend iface]
├─ mock
├─ local daemon
├─ ollama
├─ openai / openrouter
└─ anthropic cloud
[Grammar/Schema builder] → GBNF / json_schema ─────────────────────────────┘
stdout ← [Assembler / schema-invariance guard]
The components
| Component | Responsibility |
|---|---|
| Reader / Framer | Stream framing: single JSON, NDJSON, and raw lines (awk-mode). Supported three-phase semantic input is grouped into complete-frame byte-budgeted windows; pure-jq and interleaved paths remain streaming. |
| Desugar | Rewrites =~ / !~ into sem_match calls with a jq-aware lexer. |
| Planner | Exhaustive AST walk that discovers every semantic call site → a Plan used for --explain, validation, and cost estimation. |
| Executor | Runs the plan in three phases (harvest / resolve / execute), with interleaved fallback for gated unbounded value ops. |
| Semantic Executor | Deduplicates judgements, consults the persistent judgement cache when enabled, builds each op’s prompt and schema, and records run stats. |
| Backend | Pluggable inference. Shipped backends: mock, local (llama-server daemon), ollama, openai, openrouter, and anthropic/--cloud. |
| Grammar / schema builder | Turns an op’s return type / enum into a JSON Schema and, for the local engine, GBNF grammar constraints. |
| Assembler | Reassembles the output stream and enforces schema invariance. |
Windowed semantic execution
For supported three-phase semantic queries, the executor retains one complete-frame window
at a time, harvests its semantic judgements, resolves deduplicated cache misses in one
backend batch, then executes frames in source order. The default source-byte budget is
256 KiB and can be overridden with --window-bytes, AJQ_WINDOW_BYTES, or
window_bytes in TOML. A record that exceeds the budget is a valid one-frame oversized
window, so the budget is not a rejection limit. The executor does not retain the complete
stream: it keeps the current window, one lookahead frame, and bounded framing buffers.
The Backend seam
Inference sits behind a deliberately small interface, so backends are swappable without touching the planner or executor:
type Backend interface {
Judge(ctx, batch []Judgement) ([]Result, error) // grammar/schema-constrained
Warm(ctx) error
}
A judgement carries the op, kind, return type, schema/enum constraints, specs, model ID,
and the canonical value. Results carry the value plus a per-item error. Resolve enforces
stable ordering, len(results) == len(batch), return/schema validation, and only caches
successful items. The deterministic mock backend powers tests and --explain estimates;
local, Ollama, OpenAI-compatible, and Anthropic backends preserve the same structural
contract through GBNF or provider JSON-schema/structured-output mechanisms.
Inference model
The local backend runs llama-server (llama.cpp) as a warm, lazily-spawned daemon —
chosen because ajq stays a pure-Go binary (no CGo, clean cross-compilation) and avoids
the 1.5–3 s cold model-load per invocation that would make a “filter” unusable. Outputs
are constrained by a JSON Schema / GBNF grammar derived from each op’s schema, so
structural correctness is a cross-backend invariant (see
the determinism contract).
Remote and alternate-local backends share that same seam: Ollama uses its native
/api/chat endpoint with format JSON schema, OpenAI/OpenRouter use OpenAI-compatible
/v1/chat/completions, and Anthropic uses Messages API structured outputs. Backend
selection, model identity, base URL, cost caps, and persistent-cache behavior are resolved
by the CLI/env/config layer before execution starts.
Where to go next
- Split execution — the idea the architecture serves.
- The three-phase executor — the executor in detail.
- Project status — the shipped capabilities at a glance.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.