Natural Language Tool State

2026-05-22#agent-runtime#tool-use#bash#self-prompting#tool-output

Background

This observation comes from a screenshot about Claude's bash command style. The core claims:

  • When Claude generates a bash command, it often writes a comment line first, then the actual command.
  • It also frequently uses echo, or && echo "success" || echo "failed", to translate exit codes into natural language.
  • These seemingly redundant bits of text are actually exploiting natural language as the state representation a model consumes most easily.
  • When designing agent tool use, tool input can carry a description / intent first, and tool output can return natural-language state rather than bare data only.

What checking the claims found

The direction is right, but it splits into three layers.

Layer one: bash comments are indeed ignored by the shell, but they stay in the context the model has already generated. The GNU Bash manual states that in a non-interactive shell, a word beginning with # causes that word and all remaining characters on the line to be ignored. So when the model generates a comment line before the command, the comment doesn't change shell execution — but it pins the intent first, inside the model's own generation trajectory.

Layer two: natural-language intermediate state helping model action has paper and engineering-doc support. Chain-of-Thought shows intermediate reasoning steps improve complex reasoning; ReAct goes further and interleaves reasoning traces with actions, letting the reasoning trajectory help the model track and update plans and handle exceptions. That explains why "first say in one plain sentence what you're about to do" tends to make the following tool call more stable.

Layer three: the natural-language quality of tool descriptions and tool output genuinely affects model performance. Anthropic's docs say explicitly that a tool description should explain what the tool does, when to use it, what the parameters mean and its limitations, and call a detailed description an important factor in tool performance. OpenAI's function calling docs likewise note that tool definitions are injected into the model's context, and that function output is usually returned as a string — JSON, an error code, or plain text — which the model interprets as needed.

So the more accurate statement is not "natural language beats structured data", but:

For an LLM, structured state carries executability; natural-language state carries explainability and a prior for the next action.

A point that needed correcting

The screenshot claims the OpenCode bash tool has a description parameter. That example needs correcting.

I checked the current opencode-ai/opencode source (2026-05-22, commit 73ee493) and README: the built-in bash tool's parameters are command and timeout; there is no per-call description field. It does have a long tool-level description, the command parameter has its own description, and the system prompt asks the model to explain what a non-trivial bash command does and why before running it.

So OpenCode's real design is closer to:

  • Front-load semantics into the tool schema / tool description.
  • Have the model explain non-trivial commands in the assistant prose.
  • Keep the bash tool input itself centered on command.

If we design our own custom bash tool, we can still add a description / intent field — but it can't be presented as an existing fact about OpenCode's built-in bash tool.

Design suggestions

Tool input

For tools that are high-risk, long-running, or mutate system state, add an explicit intent or description field to tool input:

{
  "intent": "检查 dev server 是否被 3000 端口占用,并找出占用进程",
  "command": "lsof -i :3000"
}

This field is not for the shell. It makes the model compress the action's intent into one stable natural-language sentence before generating command. It is a mini ReAct thought at the tool-call level — but one better suited to being logged, audited and approved by the runtime.

Tool output

Don't return only:

{"exit_code": 1}

A better return is structured state plus a natural-language summary:

{
  "status": "failed",
  "exit_code": 1,
  "summary": "部署失败:3000 端口已被 nginx(pid 8432) 占用。",
  "suggested_next_steps": ["改用其他端口", "确认后停止占用进程"]
}

The runtime can still rely on status / exit_code for deterministic control, while the model gets a high-probability path for its next action straight from summary and suggested_next_steps.

The pitfall in the bash echo pattern

cmd && echo "success" || echo "failed" is friendly to the model but not necessarily to the runtime: the last command executed is echo, so the final exit code of the whole shell command may become 0, masking the original command's failure.

The sturdier approach is for the tool wrapper to capture the original exit code and return, together:

  • the original stdout / stderr
  • the original exit_code
  • a normalized status
  • a model-facing natural-language summary
  • optional suggested_next_steps

In other words: don't use echo to stand in for a state machine; let the wrapper translate machine state into state both humans and models can read.

Article angles worth developing

  • Title direction: Why agent tool output shouldn't return only exit_code
  • Central question: is a tool result machine state, or context for the model to keep generating from?
  • Key contrast: bare status codes, natural-language summaries, structured diagnosis — who does each serve?
  • Connects to existing threads: tool call role design, Trace Log, multi-agent veto boundary
  • Closing point: a good tool result should be simultaneously judgeable by a program, auditable by a human, and actionable by a model

References