履歴を毎手再送するAIエージェントで累積入力コストが二次的に増える理由と、対応する漏えいパターンをcommit時に止めるゲートの実装 #Python - Qiita
@ZEUS_LAB(ZEUS LAB)
Why cumulative input costs grow quadratically in an AI agent that resends the conversation history on every turn, and an implementation of a gate that stops the corresponding leakage patterns at commit time
  • Git
  • GitHubActions
  • Python
  • Security
  • AI
TAG28@@
TAG30@@0
TAG34
TAG37@@Last updated at 2026-11-11Posted at 2026-11-11
TAG42
TAG44

The bill for work delegated to AI becomes orders of magnitude larger only on the days it runs for a long time. Published repositories and distribution ZIPs were left with paths containing your username and secret keys. In both cases, you only notice after the fact.

First, you can check in 30 seconds → https://zero-trace-ship-kit.zeus-lab-global.workers.dev/
Place a folder or ZIP that is about to be published, and it will list any remaining traces on the spot. It runs entirely in your browser and makes no external communications.

What you will learn in this article

  • Why cumulative input costs grow quadratically with the number of turns under the AI agent usage-cost calculation model, in which the conversation history is resent on every turn. This is shown with equations and calculated values.
  • Why a “notification” alone will not stop it, and a minimal implementation that estimates the cost before the call and stops it.
  • An implementation of a gate that automatically blocks, at the commit / push stage, residual personal traces likely to remain in repositories or distribution ZIPs (paths containing usernames, secret keys, email addresses, photo EXIF data, etc.), together with its validation results.

All monetary amounts in the article are calculated values derived from the formulas, not figures from an actual invoice. The unit prices are illustrative examples, so replace them with your service’s current official prices when reading.

I have included a demo you can try entirely in your browser (no external communications): https://zero-trace-ship-kit.zeus-lab-global.workers.dev/

1. How the bill balloons

On every turn, the agent resends all the exchanges so far and the results of tool executions as input. Define the variables as follows.

  • B: the amount included from the start (instructions and tool definitions)
  • A: the amount added per turn (tool results and the immediately preceding response)
  • O: output per turn
  • S: number of turns

The input for turn k consists of B + (k-1)·A tokens.The total input through S turns is

Total input = S·B + A·S·(S-1)/2

, and the second term contributes in proportion to S squared. When the number of turns doubles, this term becomes approximately four times as large.

Using B=6,000, A=2,500, O=500, input at $3 / 1 million tokens, output at $15 / 1 million tokens, and reuse (prompt cache reads) at $0.30 / 1 million tokens as the example values, the calculation is as follows.

Number of turns Total input (tokens) Total cost When reuse applies every time Average per turn ÷ first turn
10 172,500 $0.59 $0.20 2.3×
20 595,000 $1.94 $0.47 3.8×
40 2,190,000 $6.87 $1.24 6.7×
80 8,380,000 $25.74 $3.66 12.6×
160 32,760,000 $99.48 $12.12 24.4×

When the number of turns increases 16-fold, from 10 to 160, the cost becomes approximately 169 times as high. This pattern is the reason why “a process that cost only a few tens of yen until yesterday changes the number of digits only on days when it is left running for a long time.”

Three things can be inferred.

  1. What determines the cost is not so much the unit price as the number of turns and the incremental amount per turn. Even if the unit price is halved, a 1.5-fold increase in the number of turns cancels that out.
  2. Reuse (caching) is effective, but the quadratic pattern does not disappear. Even in the table above, the column with reuse continues to accelerate. Also, if the beginning of the context is rewritten midway through, reuse is invalidated, and the normal unit price applies again from that turn onward. With a pricing model that charges a premium for cache writes, the costs will be higher than those in this column.
  3. Reducing the size of tool results is the most effective measure. A is the coefficient of the quadratic term.Changing a tool that returns the full text of a file or the entire log so that it returns only the necessary lines directly reduces the total.

2. Stop before execution rather than relying on notifications

The “budget notification” found on many admin dashboards arrives only after the budget has been spent. To prevent this, you need to estimate the worst-case cost before making the call and skip the call if it exceeds the limit.

class BudgetExceeded(RuntimeError): pass class Guard: def __init__(self, cap_usd, in_per_mtok, out_per_mtok): self.cap, self.pin, self.pout, self.spent = cap_usd, in_per_mtok, out_per_mtok, 0.0 def authorize(self, input_tokens, max_output_tokens):worst = (input_tokens * self.pin + max_output_tokens * self.pout) / 1_000_000 if self.spent + worst > self.cap: raise BudgetExceeded(f"spent ${self.spent:.4f}, next call up to ${worst:.4f}") def record(self, input_tokens, output_tokens):self.spent += (input_tokens * self.pin + output_tokens * self.pout) / 1_000_000

The key points are the following three.

  • authorize is estimated assuming the output limit is fully used. If it is judged by the worst-case value rather than actual results, no call exceeding the limit will be executed even once.
  • Pass the actual usage included in the response to record.
  • The limit applies not only to "a single execution" but also on a monthly basis. If multiple processes read the same ledger file, they will be stopped when the combined total reaches the limit.

In the previous example, setting the limit to $2 stops it at the 21st step. The difference from $6.87 if it is left running for 40 steps is the amount protected in a single execution.

Repeatedly making the same call (reading the same file many times or repeating the same search) is also a typical way for costs to balloon. This can be prevented with a guard of just a few lines: concatenate the tool name and arguments into a string, compare it with the immediately preceding one, and stop if they are the same in succession.

3. “Traces” Left in Published Artifacts

After costs, the next most likely source of trouble is personal information left in a published repository or distributed ZIP. When you entrust work to an AI agent, information about the terminal used for the work may be written into logs, settings, error output, and test results as-is. Here are some items that tend to remain.

Type Example
Absolute paths containing usernames /Users//..., /home//..., C:\Users\\...
Secret keys / Private keys API keys for each service; keys beginning with BEGIN ... PRIVATE KEY
Email addresses Contact information in the README; commit author name
Embedded image metadata JPEG EXIF data (which may include the shooting location); text metadata in PNG files
Document author fieldPDF Author and Office document creator
Remaining OS files .DS_Store、__MACOSX/、Thumbs.db、.env
Git identity Commits created with a personal name and email address

In particular, do not overlook ZIP files. Even if you check the working folder, you do not open the ZIP archive bundled for distribution, much less a ZIP archive inside it.

4. Implementing the gate

The policy is simply: “If found, make commit and push fail.” It will be written using only Python’s standard features.

4.1 Use regular expressions to capture things with fixed patterns

HOME_PATH = re.compile( r"(?<![\w.])(?:/Users/|/home/|[A-Za-z]:\\{1,2}Users\\{1,2})([A-Za-z0-9][A-Za-z0-9._-]*)" )

The leading (?<![\w.]) is a condition that prevents accidentally capturing part of a URL such as https://example.com/home/dashboard. If the extracted name is a generic or CI name, such as you or runner, it is allowed through.

4.2 Read your own name at runtime instead of writing it into a file

If you write your real name on the machine being inspected, that machine itself becomes a source of leakage. Therefore, the login name, the full name registered with the OS, and the identity in Git’s global configuration areread from the terminal on every run,and compared only in memory. Always redact them in the output.

raw.add(getpass.getuser()) raw.add(Path.home().name)full = run(["id", "-F"]) # macOS: Registered name

Strings are searched in both UTF-8 and UTF-16 forms. This is because Office documents and some binaries have names in UTF-16.

4.3 Open ZIPs internally

def scan_archiveself, where, data, depth): with zipfile.ZipFileio.BytesIOdata)) as zf: for info in zf.infolist(): self.scan_bytes(f"{where}!/{info.filename}", zf.read(info), depth + 1)

If the contents are a ZIP, scan_bytes calls scan_archive again, so ZIPs inside ZIPs also pass through the same inspection. Since .docx and .xlsx are also ZIPs internally, the author field can be read through the same route.

4.4 It is sufficient to read image segments

Whether EXIF is present can be determined simply by reading the JPEG segments in order and checking whether the APP1 segment begins with Exif. The presence of location information is determined by whether it contains the marker 0x8825. No external library is required.

4.5 Put it in a hook

# .git/hooks/pre-commit exec python3 gatekeeper/zero_trace_check.py --staged --identity # .git/hooks/pre-push exec python3 gatekeeper/zero_trace_check.py --all --identity --history --remote --path dist

Before a commit, it examines the content that is about to be recorded (the staged content itself). Before a push, it examines all files, the identities and messages of past commits, the destination URL, and the location of the distribution ZIP excluded by .gitignore. The same inspection is also run as the first job in GitHub Actions, so that subsequent distribution proceeds only if it passes.

5. Verification results

It would be meaningless for the checker itself to declare itself "pass," so we verified it using a separate program.Create a “file with an intentional leak” and a “confusable but safe file” in a temporary folder and run them through the inspection tool. Because the embedded values are assembled at runtime, the test program itself passes the inspection.

26 of 26 detected0 false positives out of 15
Item Result
File with an intentional leak
Confusable but safe file
Time required 6.9 milliseconds (Python 3.14, Apple Silicon Mac, one run)

The 26 planted cases include cryptocurrency recovery phrases (12-word and 24-word), an EVM private key assigned as a key, a Solana key file, an exchange API secret, absolute paths for three operating systems, four types of keys and private keys, an email address, a hard-coded password, a JPEG with EXIF and location data, a PNG with textual information, .DS_Store, .env, a PDF with an author field, a ZIP containing paths, a “ZIP within a ZIP” containing keys, a ZIP containing __MACOSX, a truncated ZIP, a .docx with a creator field, a binary file containing a registered term in UTF-16, and a filename containing a registered term.

The 15 safe-side cases include a SHA-256 value (64 hexadecimal digits, the same as for a key), ordinary English prose containing a word from a word list, an illustrative path such as /Users/you/, actions/checkout@v4, logo@2x.png, an example.com address, a URL containing /home/, and code that only reads from environment variables.

The test results are published as-is: https://zero-trace-ship-kit.zeus-lab-global.workers.dev/evidence/selftest.json

6. Limitations

  • Information with no defined format cannot be detected. The only real names and addresses in prose that are in scope are terms read from the device or terms added to the local registry.
  • Key detection is limited to the major formats. Credentials in custom formats are out of scope.
  • Text embedded within images is not read.
  • “0 detections” means that no instances in formats known to this inspection tool remain; it is not proof of safety.

Summary

  • Agent costs grow in the form S·B + A·S·(S-1)/2. What matters is the number of steps and the increase per step.
  • To stop it, estimate the worst case before making the call and reject it. Do not suppress notifications.
  • Block personal traces at the commit / push gate rather than relying on human vigilance. Open ZIPs all the way through. Do not write your own name into the inspection tool; read it at runtime.

The browser-based scanner and the lightweight version for checking text files are free: https://zero-trace-ship-kit.zeus-lab-global.workers.dev/

The full version, which includes a full-featured checker that blocks commits/pushes, a tool for creating clean distribution ZIPs, limit guards, a complete suite of distribution automation tools, and a commercial license, is available here: https://zeuslab.gumroad.com/l/zero-trace-ship-kit