Why this piece exists
Automation gets easier when success and failure share one shape.
If a script prints plain text on success and crashes or dumps traceback text
on failure, every caller has to guess what happened. That means custom glue,
brittle parsing, and a surprising amount of defensive code around something
that should be simple.
A JSON envelope fixes that. The caller checks ok, reads
items, summary, and errors, and moves
on. Real APIs work the same way. They do not expect you to scrape random
strings from stderr. They hand back structured data the next layer can make
decisions from.
Expected output
What a successful run looks like.
Success case:
{
"ok": true,
"tool": "term_count",
"input": {
"term": "agent",
"files": [
"sample_docs/agents.txt"
]
},
"items": [
{
"file": "sample_docs/agents.txt",
"count": 2
}
],
"summary": {
"total": 2
},
"errors": []
}
Failure case with a bad path:
{
"ok": false,
"tool": "term_count",
"input": {
"term": "agent",
"files": [
"sample_docs/does-not-exist.txt"
]
},
"items": [],
"summary": {
"total": 0
},
"errors": [
{
"file": "sample_docs/does-not-exist.txt",
"error": "missing_file"
}
]
}
The fields worth noticing are ok, which tells the caller whether
the run succeeded, items, which holds per-file results,
summary, which gives the total count, and errors,
which stays present even when it is empty. That last part is what keeps the
schema easy to consume.
What you just built
A boundary other tools can trust.
You took a useful little script and made its output dependable. That is the
real upgrade here. The counting logic matters less than the fact that callers
now get a stable envelope they can inspect without guessing.
In practice, this is how a lot of AI tooling grows up. A quick script becomes
a wrapper, the wrapper becomes a contract, and that contract becomes something
protocols and agents can safely build around.