Skip to content

chant search

chant search "<query>" [--live | --at <ref>] [--env <name>] [flags]

chant search answers a question about your estate with a compact result instead of the whole graph. Each match prints as one line — logical id, kind, physical id when known, and any attributes you asked for. Tens of tokens, not thousands.

That compactness is the point. An agent asking “which instances sit in public subnets” should not ingest a multi-thousand-token IR dump and join instance -> subnet -> route table by hand — it should run one query and read a few rows. chant search is the query surface the agent integration leans on, and it is what kept chant’s token footprint the lowest of any tooling configuration on aws-bench, an infrastructure-agent benchmark.

By default the query runs over the declared graph (source only, offline). With --live --env <name> chant observes the environment, overlays live identity and attributes onto the declared graph, and answers from that — same canvas as chant graph --overlay.

Space-separated terms. All terms must match (AND). Quote phrases to keep them together.

TermMatches
wordcase-insensitive substring over id, kind, and attribute values
kind:<substr>node kind contains <substr> (e.g. kind:EC2::Instance)
attr:<name>attribute <name> is present
attr:<name>=<val>attribute <name> equals/contains <val>
tag:<key>=<val>a Tags entry with Key=<key> and Value containing <val>
->kind:X, ->attr:…, ->tag:…node has an edge to a node matching the right side
<-kind:X, <-attr:…, <-tag:…node has an edge from a node matching the right side

The edge operators make the search edge-aware: a traversal becomes one term instead of a hand-join across results.

Terminal window
# instances that reference a public subnet
chant search "kind:Instance ->attr:MapPublicIpOnLaunch=true"

Before matching, chant folds derived reachability facts onto instance nodes (effective-topology enrichment), so multi-hop and launch-template joins become one node predicate:

  • internetFacing — the instance’s subnet routes to an internet gateway (resolved across the account default VPC in live mode)
  • effectiveIngress — normalized ingress rules from all reachable security groups, attached directly or via the instance’s launch template
Terminal window
chant search "kind:EC2::Instance attr:internetFacing=true" --live --env dev
chant search "kind:EC2::Instance attr:internetFacing=true attr:effectiveIngress=tcp:22:0.0.0.0/0" --live --env dev --explain --show InstanceId
FlagTypeDefaultDescription
--livebooleanfalseQuery live state overlaid on the declared graph (requires --env)
--atstringAnswer from a recorded snapshot instead of reading the estate now (latest; requires --env; mutually exclusive with --live)
--envstringEnvironment to observe (must be in chant.config environments when declared)
--srcstringconfig sourceDir or .Source directory to discover/build
--showstringComma-separated attribute names to append to each result row
--explainbooleanfalsePrint the match-universe footer and near-miss exclusions
--ambientbooleanfalseAlso report resources of a managed kind that exist without being declared or referenced
--check-livebooleanfalseWith --at: additionally read the estate live and diff the matched rows against the snapshot the answer came from
--check-snapshotbooleanfalseWith --live: additionally diff the matched rows against the most recently recorded snapshot
--fail-on-driftbooleanfalseWith --check-live/--check-snapshot: exit non-zero when the scoped check finds drift

The footer gives a small model a reason to trust the result instead of re-deriving it with a lossy CLI sweep:

  • the universe count — 4 of 6 EC2::Instance matched, a denominator the typed graph knows and a live sweep does not
  • inclusion evidence for derived facts — e.g. ✓ web1 internet-facing via subnet route
  • up to 8 near-misses with the query term each one fails
— 2 of 6 EC2::Instance matched (query: kind:EC2::Instance attr:internetFacing=true)
✓ BastionUsEast1 internet-facing via default-vpc
· excluded AppUsWest2 — fails attr:internetFacing=true

Everything in the footer is a property of the query over the graph, not of any expected answer.

--live builds the project first (a failing build stops the search), observes owned resources in the environment, and overlays live identity onto the declared graph — the declared side carries the topology so ->/<- resolve, the live side supplies physical ids and live-only attributes.

Multi-stack projects are observed per stack, and results display the bare logical id without the stack qualifier.

Every answer ends with a provenance line saying what backed it: declared only, observed live · bound N/M, or observed from snapshot. A live read that fails (unreachable endpoint, denied credentials) is reported as a failure rather than an empty estate. Each row the read could not cover prints (unobserved: read-failed) where its physical id would go, stderr names the lexicon and the underlying error, the footer reads live read failed (<lexicon>) · N/M rows unobserved, and the command exits 1. Derived attributes that fold over live topology are not advertised in that case, since nothing was read to compute them. The rows printed are what the source declares, labelled as such; answer from a recorded snapshot with --at latest or drop --live for a declared-only query.

--check-live and --check-snapshot compare the matched rows — and only the matched rows — against the observation the primary answer did not use. Where lifecycle diff --live asks “has anything changed anywhere”, this asks “is what I was just told still true”, about the resources in front of you, in the same command that produced them.

$ chant search "kind:EC2::Instance" --env prod --at latest --check-live
webServer AWS::EC2::Instance i-e392157f576caf855
launchTemplateServer AWS::EC2::Instance i-ee2744528df10ea0d
⚠ webServer attributes.SubnetId: subnet-2faec80b → subnet-9f01ab3c — drifted
— checked against a live read · 1 of 2 matched drifted

The reverse framing answers live and checks the record for staleness:

Terminal window
chant search "kind:EC2::Instance" --env prod --live --check-snapshot

Both reuse diffLive — the exact function lifecycle diff --live calls — scoped to the matched entity ids, so the categories and what counts as drift are identical: missing/orphan/disappeared/drifted are drift, newlyObserved and runtime children are reported but are not, and unobserved (a read that could not look) stays distinct from both. A resource the query scoped out is a fourth thing again — never reported as missing or unobserved, because it was never asked about.

--check-live requires --at; --check-snapshot requires --live — each checks the answer against the source it did not already use. --fail-on-drift exits non-zero when the check finds drift, so it is usable as a CI gate; a missing snapshot for --check-snapshot is a note on stderr, not a failure — the live answer already stands. A snapshot recorded with chant lifecycle snapshot <env> --deep compares here at identity depth regardless, and the footer says so, rather than reporting every property the identity-depth side never recorded as drift.

Terminal window
# offline, over the declared graph
chant search "kind:Bucket"
chant search "tag:Environment=prod"
# live estate questions
chant search "kind:EC2::Instance attr:internetFacing=true" --live --env dev
chant search "kind:Instance ->attr:MapPublicIpOnLaunch=true" --live --env dev --explain
# query-scoped drift
chant search "kind:EC2::Instance" --env prod --at latest --check-live
chant search "kind:EC2::Instance" --env prod --live --check-snapshot --fail-on-drift
CodeMeaning
0Query ran (including zero matches — prints (no matches)), including a scoped check that found drift without --fail-on-drift
1Missing query, missing/unknown --env, build failure in --live mode, a --live/--check-live read that failed for at least one lexicon (rows are printed, marked unobserved), --check-live/--check-snapshot used without its required flag, --fail-on-drift used without either, or --fail-on-drift with drift found