How to fix Agent executor error and Message truncation failure in Antigravity?

Your agent crashes with a truncation failure during execution. The log shows agent executor error: could not convert a single message before hitting truncation on version 1.20.5.

Agent executor message truncation failure on v1.20.5

You are seeing intermittent failures where the agent cannot even convert the first chat message because the conversation is already beyond the truncation budget.

The error text is: could not convert a single message before hitting truncation and it is wrapped inside an agent executor error.

This typically appears with a stack trace similar to:

  • google3/third_party/jetski/cortex/cortex.(*CascadeManager).executeHelper.func1 at third_party/jetski/cortex/cascade_manager.go:2096
  • google3/third_party/jetski/cortex/chatconverters/chatconverters.init at third_party/jetski/cortex/chatconverters/trajectory_chat_converter.go:44

Solution Overview

AspectDetail
Root CauseThe chat converter is handed a context that already exceeds the model’s token budget, often due to an oversized system prompt, tools schema, or metadata. With a strict truncation policy, zero messages survive trimming.
Primary FixReduce prompt footprint and enforce safe truncation so at least one user message or a minimal system prompt always passes. Also update to the latest patch if available or temporarily pin to the last known good version.
ComplexityEasy
Estimated Time15 to 30 minutes

If you orchestrate multiple agents, an agent manager pattern helps enforce shared context limits and trimming policies which prevents this class of failure at scale.

Fix message truncation failure in the agent executor

Step-by-Step Solution

1. Confirm and capture the failure footprint

  • Find occurrences across logs to verify frequency and scope:
grep -R "could not convert a single message before hitting truncation" -n /var/log 2>/dev/null
  • Capture the full executor trace for one failing run:
grep -R "agent executor error" -n /var/log | head -50

2. Shrink the system prompt and headers to guarantee headroom

  • If you maintain a system prompt or preamble, trim it aggressively. Keep tool descriptions and policies concise.
  • A simple guard that enforces a hard cap for the system prompt helps:
def clamp(text: str, max_chars: int = 2000) -> str:
 return text[:max_chars]
SYSTEM_PROMPT = clamp(BASE_SYSTEM_PROMPT)
  • For Python chat stacks, also cap tool schemas:
import json
def compact_tool_schema(schema: dict, max_chars: int = 6000) -> dict:
 s = json.dumps(schema)
 if len(s) <= max_chars:
 return schema
 # crude size trim: drop verbose descriptions
 for tool in schema.get("tools", []):
 if "description" in tool and len(tool["description"]) > 300:
 tool["description"] = tool["description"][:300]
 return schema

3. Pre trim conversation turns by tokens before calling the agent

  • Use a token counter and drop earliest turns until you fit under a target budget that reserves headroom for the first conversion pass.
# pip install tiktoken
import tiktoken
def token_len(text: str, model: str = "gpt-4o-mini") -> int:
 enc = tiktoken.get_encoding("cl100k_base")
 return len(enc.encode(text))
def trim_messages(messages, max_tokens: int, reserve: int = 512):
 # messages is a list of dicts like {"role": "user"|"system"|"assistant", "content": "..."}
 # keep the latest turns while leaving reserve for tool calls and converter overhead
 budget = max_tokens - reserve
 out = []
 total = 0
 for msg in reversed(messages):
 tl = token_len(msg["content"])
 if total + tl > budget:
 break
 out.append(msg)
 total += tl
 return list(reversed(out))
  • Call the trimmer with your model limit minus a reserve:
MAX_CTX = 128000
messages = trim_messages(messages, MAX_CTX, reserve=1024)

Reference: tiktoken library and LangChain message trimming patterns.

4. Enforce safe truncation in the converter or adapter

  • Ensure the converter does not drop every message. Keep at least a minimal system role or the latest user message.

Example in guarantee one message survives:

func SafeTruncate(msgs []Message, budget int) []Message {
 kept := make([]Message, 0, len(msgs))
 used := 0
 for _, m := range msgs {
 tl := EstimateTokens(m.Content)
 if used+tl > budget {
 continue
 }
 kept = append(kept, m)
 used += tl
 }
 // Guarantee at least one message survives
 if len(kept) == 0 && len(msgs) > 0 {
 // Prefer last user message
 last := msgs[len(msgs)-1]
 if last.Role != "user" {
 // search backwards for a user turn
 for i := len(msgs) - 1; i >= 0; i-- {
 if msgs[i].Role == "user" {
 last = msgs[i]
 break
 }
 }
 }
 kept = []Message{{Role: last.Role, Content: SafeHead(last.Content, 512)}}
 }
 return kept
}
  • Also add a small head truncator to avoid empty content:
func SafeHead(s string, maxRunes int) string {
 r := []rune(s)
 if len(r) <= maxRunes {
 return s
 }
 return string(r[:maxRunes])
}

5. Update to the latest patch or temporarily pin to a known good build

  • If 1.20.5 introduced a regression, update your runtime to the latest patch. If you must roll back, pin to the previous version used before the failures.

Examples:

# Python based runtime
pip install -U your-agent-runtime

Node based runtime

npm i your-agent-runtime@latest

Container based runtime

docker pull yourorg/agent-runtime:latest && docker run yourorg/agent-runtime:latest

If your environment integrates Claude tools inside Antigravity, see installing Claude Code in Antigravity for a clean reinstall path that often resolves mismatched component versions.

6. Reserve explicit model headroom

  • Many models need margin for system tokens and tool calls. Reserve at least five to ten percent of the context window.

Reference model limits:

Alternative Fixes and Workarounds

Fallback to summary plus last user turn

  • Before sending the full history, summarize older content then append the latest user turn verbatim:
def summarize(history: list[str]) -> str:
 # replace with your local summarizer
 return "Summary: " + " ".join(h[:200] for h in history)[-2000:]
summary = summarize([m["content"] for m in messages[:-1]])
messages = [
 {"role": "system", "content": "Conversation summary\n" + summary},
 messages[-1], # last user turn
]

Disable verbose debug metadata in inputs

  • Strip large JSON dumps, stack traces, and telemetry from the prompt. Store references in external logs and only pass short identifiers.

Guardrail on attachment size

  • Reject or downsample giant attachments before prompting. Replace with short captions and links to the asset store.

If you are coordinating multiple agents with shared tools, an orchestration pattern helps enforce global context budgets.

See agent manager guidance for Antigravity to apply budget enforcement once for all agents rather than per tool.

Troubleshooting Tips

  • Verify your token estimator matches the target model. A mismatch can undercount and trigger hard truncation later.
  • Confirm the converter runs before any tool expansion that may inflate tokens.
  • Inspect the first failing turn. If it is a tool schema or system preamble thousands of tokens long, trim that first.
  • Check for duplicated history caused by retries. De duplicate by message id.
  • Run a dry run that prints token counts per message before sending to the model.

Best Practices

  • Keep the system prompt under a strict character or token limit with automated checks in CI.
  • Cap tool descriptions and JSON schemas. Prefer references to docs instead of embedding full schemas.
  • Always reserve headroom in your budget function.
  • Add a unit test that asserts at least one user turn survives after truncation.
  • Centralize trimming so every agent call flows through the same policy.
  • For teams weighing tool stacks, see our concise comparison of Claude Code and Antigravity approaches to pick defaults that keep prompts compact.

Final Thought

This failure happens when the model context is already full before conversion even starts. By shrinking prompts, adding a safe truncation rule that always preserves one turn, and updating to the latest patch, you restore stable agent execution fast.

Leave a Comment