The first night I used quoting in Antigravity 2.12.0, I picked four lines of a function out of a long response and handed them to the next prompt. I thought I had trimmed the context nicely.
What came back was a fix built around a constant I had never sent — invented, plausibly named, and wrong. I ran the same exchange three times before my hands stopped.
The cause was not subtle. The names those four lines relied on had their definitions outside the quote.
What I want to say first is this: the thing to trim when you quote is not length.
What quoting carries, and what it leaves behind
Quoting, added in 2.12.0, lets you highlight part of a response and pass it directly as context for your next prompt. Before that, the choice was copying by hand or referencing the whole response. When only one passage matters, neither option sits well.
In practice, though, what the feature carries is a run of consecutive lines. The definitions those lines lean on do not come along.
Here is what I quoted first:
def run_agent(settings):
timeout = settings.get("timeout", AGENT_TIMEOUT_MS)
budget = min(timeout, AGENT_TIMEOUT_MS)
return dispatch(budget)It looks self-contained. But neither the value of AGENT_TIMEOUT_MS nor what dispatch returns appears anywhere in those four lines. A human reader fills the gap with "that was defined further up." The next prompt receives only the selection.
If you pair Antigravity with a local model, this bites harder. Trimming the range to fit a narrow context window is exactly when you drop a dependency you needed.
Counting what got left behind, before you send
To stop repeating the mistake, I wrote a small script that runs the moment I have picked a range. Save the response as text, hand it the line range, and it lists the names that range touches whose definitions sit outside it.
#!/usr/bin/env python3
"""Find names whose definitions get left behind when you quote part of a
response into your next prompt.
Usage: quote_check.py <response.md> <start> <end> ["follow-up text"]
Exit code: 0 = nothing left behind / 1 = something left behind
"""
import re
import sys
# Lines we treat as definitions (Python / JS / TS / constants)
DEF_PATTERNS = [
re.compile(r"^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)"),
re.compile(r"^\s*class\s+([A-Za-z_]\w*)"),
re.compile(r"^\s*(?:export\s+)?(?:const|let|var|function)\s+([A-Za-z_]\w*)"),
re.compile(r"^\s*([A-Z][A-Z0-9_]{2,})\s*[:=]"),
]
BACKTICK = re.compile(r"`([A-Za-z_][\w.]*)`")
BARE = re.compile(r"\b([A-Za-z_]\w{2,})\b")
STOPWORDS = {
"def", "class", "return", "import", "from", "for", "if", "else", "elif",
"None", "True", "False", "self", "and", "not", "get", "min", "max", "len",
"sorted", "list", "dict", "str", "int", "encoding", "utf",
}
def read_lines(path):
with open(path, encoding="utf-8") as fh:
return fh.read().splitlines()
def collect_defs(lines, offset=0):
"""Collect defined names and the line each one appears on."""
found = {}
for i, line in enumerate(lines, start=offset + 1):
for pat in DEF_PATTERNS:
m = pat.match(line)
if m and m.group(1) not in found:
found[m.group(1)] = i
return found
def collect_refs(text, known):
"""Keep only names this text touches that are defined somewhere in the response."""
hits = set()
for tok in BACKTICK.findall(text):
hits.add(tok.split(".")[0])
for tok in BARE.findall(text):
if tok not in STOPWORDS:
hits.add(tok)
return {t for t in hits if t in known}
def report(path, start, end, followup=""):
lines = read_lines(path)
if not 1 <= start <= end <= len(lines):
print(f"Invalid range (use 1..{len(lines)})")
return 2
quoted_lines = lines[start - 1:end]
quoted = "\n".join(quoted_lines)
whole = "\n".join(lines)
all_defs = collect_defs(lines)
in_quote = collect_defs(quoted_lines, offset=start - 1)
referenced = collect_refs(quoted + "\n" + followup, all_defs)
dangling = sorted(t for t in referenced if t not in in_quote)
ratio = len(quoted) * 100 // max(len(whole), 1)
print(f"whole response : {len(whole)} chars / {len(lines)} lines")
print(f"quote L{start}-L{end} : {len(quoted)} chars / {end - start + 1} lines ({ratio}% of whole)")
print(f"names touched : {len(referenced)}")
print(f"defined in quote : {len(in_quote)}")
if dangling:
print(f"defined outside : {len(dangling)}")
for tok in dangling:
print(f" - {tok} (defined at L{all_defs[tok]})")
lo = min(start, min(all_defs[t] for t in dangling))
hi = max(end, max(all_defs[t] for t in dangling))
print(f"-> widening to L{lo}-L{hi} clears this")
return 1
print("defined outside : 0")
return 0
if __name__ == "__main__":
if len(sys.argv) < 4:
print(__doc__)
sys.exit(2)
tail = sys.argv[4] if len(sys.argv) > 4 else ""
sys.exit(report(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), tail))Two notes on why it is written this way.
First, reference extraction does not rely on backtick notation alone. Prose in a response wraps dispatch in backticks, but the code body uses it bare. Watching only the wrapped form skips exactly the dependency you most wanted to catch.
Second, the check is limited to names defined somewhere in the same response. Pull in every library function and the output becomes unreadable. What matters here is only what was present in the response and missing from the quote.
Four quoted lines, two definitions left behind
I prepared a 45-line, 1,039-character response and ran the check on the same four lines of run_agent I had quoted originally.
$ python3 quote_check.py response.md 33 36 "fix how budget is chosen"
whole response : 1039 chars / 45 lines
quote L33-L36 : 152 chars / 4 lines (14% of whole)
names touched : 3
defined in quote : 1
defined outside : 2
- AGENT_TIMEOUT_MS (defined at L6)
- dispatch (defined at L38)
-> widening to L6-L38 clears this
$ echo $?
1I had trimmed to 14% of the response, and two of the three names in that slice had their definitions left behind. The answer missed not because the model was weak, but because what I handed over was incomplete.
Widening once was not enough
I assumed that obeying the suggestion and jumping to L6-L38 would settle it. It did not.
$ python3 quote_check.py response.md 6 38 "fix how budget is chosen"
quote L6-L38 : 807 chars / 33 lines (77% of whole)
names touched : 8
defined in quote : 7
defined outside : 1
- CONFIG_ROOT (defined at L5)
-> widening to L5-L38 clears thisWidening pulls in new lines, and those new lines lean on names of their own. Dependencies form a chain, and one pull does not reach the end. That part genuinely surprised me.
So I added a short wrapper that widens until the check comes back clean.
#!/bin/bash
# Usage: widen.sh <response.md> <start> <end> "<follow-up text>"
FILE="$1"; S="$2"; E="$3"; MSG="$4"
for i in 1 2 3 4 5; do
OUT=$(python3 quote_check.py "$FILE" "$S" "$E" "$MSG")
if ! grep -q 'defined outside : [1-9]' <<< "$OUT"; then
echo "converged on pass $i: L$S-L$E"
grep -E '^quote L' <<< "$OUT"
exit 0
fi
RANGE=$(grep -o 'L[0-9]*-L[0-9]*' <<< "$OUT" | tail -1)
S="${RANGE%-*}"; S="${S#L}"
E="${RANGE#*-}"; E="${E#L}"
echo "pass $i: widening to L$S-L$E"
done
echo "did not converge in 5 passes"; exit 1Running it gave this:
$ ./widen.sh response.md 33 36 "fix how budget is chosen"
pass 1: widening to L6-L38
pass 2: widening to L5-L38
converged on pass 3: L5-L38
quote L5-L38 : 838 chars / 34 lines (80% of whole)A quote that should have been 14% ended up at 80%. On the numbers that looks like quoting lost its point, and yet I was relieved. I now knew in advance that this passage needs 80% to make sense at all.
Passages that suit quoting, and passages that do not
Within the same response, different ranges behaved nothing alike. The section handling the scan depth cutoff converged on a single pass.
| Quoted passage | Starting range | After convergence | Widening passes |
|---|---|---|---|
| Timeout decision (run_agent) | 4 lines, 14% of whole | 34 lines, 80% of whole | 2 |
| Scan cutoff (walk_guarded) | 7 lines, 19% of whole | 8 lines, 21% of whole | 1 |
| Whole response as-is | 45 lines, 100% | 45 lines, 100% | 0 |
One passage was fine at 21%; the other demanded 80%. The gap has nothing to do with line count or complexity — it tracks how much the passage leans on names defined elsewhere.
What you trim by quoting is not length. It is everything outside the dependency.
Standing on that line makes the call easy. A passage that widens to 80% was never a good candidate for quoting; referencing the whole response, or first extracting that part into its own file, gets you there faster. A passage that settles at 21% can be quoted without a second thought.
Quoting and whole-response referencing are not competing options. I keep both and choose by the converged ratio.
When the goal is reading part of a file rather than part of a response, the page range and resolution floor in view_file is the better instrument. And what actually shrinks when you split rule files with @path/to/file is something I wrote down in what splitting rules actually shrinks.
What I do now
There is only one step. Once you have selected a range, run quote_check.py before you hit send. If nothing was left behind, pass it along as-is.
I still leave something behind on roughly one attempt in three. Even so, noticing it before sending rather than after reading the reply saves an amount of time that is hard to overstate.