People ask how I built the “Grok bot” — the little agent you ask for an illustration and it hands one back inline. The honest answer deflates the magic: a bot in an agent platform is two small things — a tool (a function the model can call) and a persona (an agent that knows when to call it). Once you see that split, adding a new capability stops being a project and becomes an evening.

Here’s the anatomy, using my image generator as the worked example.

First decision: tool, MCP, or engine?

Not every capability should be a hand-written tool. I pick the cheapest shape that fits.

ShapeUse whenExample
Built-in toolI implement it in code and reuse everywheregenerate_image, web_search
Custom HTTP toolCalling an external API, no code — a URL + params in the DBan internal reporting endpoint
MCP serverThe vendor already ships a rich tool serverGitHub, Linear via MCP OAuth
EngineThe thing is the executor that runs a whole taska cloud coding agent

The image generator is a built-in tool: one function, called from anywhere, returning an artifact. That’s the sweet spot for “give the model a new verb.”

The tool is a function with a schema — and the schema is a prompt

A tool is defined twice: a schema the model reads to decide when and how to call it, and an implementation that does the work. The schema matters more than people expect — its description is the only instruction the model gets about the tool.

{
  "type": "function",
  "function": {
    "name": "generate_image",
    "description": "Generate an image from a text prompt. Returns the image inline. Use for illustrations, mockups, concept art, social visuals.",
    "parameters": {
      "type": "object",
      "properties": { "prompt": { "type": "string", "description": "Detailed description of the image to create." } },
      "required": ["prompt"]
    }
  }
}

Rules that pay off: name it as a verb, write the description as when to use it (not what it is), keep parameters few and typed, and say what it returns. A vague description is a tool the model never calls, or calls wrong.

The implementation: do the work, return something the model can use

The function itself is ordinary code. The one trick worth internalizing: return a result the model and the UI can both use. My image tool doesn’t return raw bytes — it saves the image as an artifact and returns a markdown image tag, so the answer renders inline in chat with zero extra plumbing.

def tool_generate_image(prompt):
    img = provider.generate(prompt)                 # 1. call the provider
    aid = save_artifact(img, mime="image/jpeg")     # 2. persist as an artifact
    return "![%s](/files/%s/image.jpg)" % (prompt[:60], aid)  # 3. model- & UI-friendly

Good tool returns are short, self-describing, and reference artifacts by URL rather than dumping blobs into the context window.

The tool loop: how a call actually happens

I don’t orchestrate the call — the model does. My runtime just executes what it asks for and feeds the result back until it stops.

flowchart LR
  U["User: 'draw a fox mascot'"] --> M["Model"]
  M -->|"tool_call: generate_image"| RT["Runtime"]
  RT -->|run tool_generate_image| T["Tool"]
  T -->|"![](/files/…)"| RT
  RT -->|tool result| M
  M -->|final answer + image| U

I register the implementation next to its schema so the runtime finds it by name:

TOOL_FN = { "generate_image": lambda a: tool_generate_image(a.get("prompt", "")), ... }

That’s the entire mechanism. Every “bot” you’ve admired is this loop with different tools bolted on.

From tool to bot: add a persona

A tool anyone can call becomes a bot when I wrap it in an agent — a system prompt that gives it a job, a voice, and the one or two tools it should reach for.

FieldGrok image bot
name / icon”Grok Artist” 🎨
tools["generate_image"]
system prompt”You are a visual artist. When asked for an image, write a vivid, specific prompt and call generate_image. Offer one variation.”
engine / modelthe tool-loop driver

Now “Grok Artist” is a thing on my roster I can chat with, hand to a workflow, or trigger on a schedule — all because I added one verb and one persona.

The recipe, generalized

  1. Choose the shape — tool, HTTP tool, MCP, or engine.
  2. Write the schema — verb name, a when-to-use description, few typed params, a stated return.
  3. Implement it — return a short, artifact-referencing string.
  4. Register it — schema + function, by name.
  5. Wrap it in a persona — an agent that knows when to reach for it.

I shipped an image generator, a document builder, a code sandbox, and a cloud-coding agent through these exact five steps. The platform doesn’t grow by getting bigger; it grows one verb at a time.

Export for reading

Comments