LLM Instructions
How to write an llm.txt file so AI agents know how to use your plugin correctly.
Quick reminder: how AI agents call your plugin
Your plugin's main(env) function returns an object of methods. AI agents call these methods through tools["your-plugin"].methodName(). The llm.txt file is how you teach the AI when and how to call each method. Without it, the AI has to guess — and it will guess wrong.
What is llm.txt?
llm.txt is a plain text file in your plugin's root directory. It is the instruction manual that AI agents read before they use your plugin. Think of it like a README, but written specifically for an AI that needs to make function calls on behalf of a user.
When a user asks an AI agent to do something — like "check my time entries" or "send an email" — the AI reads your llm.txt to figure out which method to call, what arguments to pass, and how to format the result. If your llm.txt is vague or missing, the AI might call the wrong method, pass bad arguments, or give the user confusing output.
Where does the file go?
Place llm.txt in your plugin's root directory, alongside package.json and permissions.json:
my-plugin/
package.json
permissions.json
llm.txt ← this file
src/
index.ts
How the AI reads your instructions
When Kazibee starts a session with an AI agent, it loads the llm.txt for every installed plugin and includes it in the AI's context. The AI reads these instructions before the user even types a message. You can also preview what the AI will see:
dev@local:~/workspace $ kazibee llm clockify
Tool: clockify
Package: clockify
...
Use this to check your instructions look right. If something is confusing to you, it will be confusing to the AI.
Anatomy of a good llm.txt
A well-structured llm.txt has six sections. Here is the Clockify plugin's llm.txt as a complete, real-world example:
Tool: clockify
Package: clockify
Use this tool for Clockify time tracking management.
Auth requirements
- CLOCKIFY_API_KEY
Runtime note
- API key is expected to be injected via `--env` (not interactive login).
- Generate your key at https://app.clockify.me/user/preferences#advanced
Common workflow
- First call getCurrentUser() to get the userId and activeWorkspace.
- Use the activeWorkspace as workspaceId for subsequent calls.
- Time entries require both workspaceId and userId.
Typical intents to map
- "who am I" -> getCurrentUser()
- "my workspaces" -> listWorkspaces()
- "list projects" -> listProjects(workspaceId)
- "my time entries" -> listTimeEntries(workspaceId, userId)
- "start timer" -> createTimeEntry(workspaceId, { start: ... })
- "stop timer" -> stopTimer(workspaceId, userId)
Code call examples
const user = await tools["clockify"].getCurrentUser();
const wsId = user.activeWorkspace;
const projects = await tools["clockify"].listProjects(wsId);
Output behavior
- Include IDs/names in responses for follow-up actions.
- When listing time entries, format durations readably.
- For running timers (end is null), indicate the timer is active.
Breaking down each section
Tool name and package
Always start with Tool: <name> and Package: <package>. This tells the AI exactly which tool to reference. Follow it with a one-line summary of what the plugin does.
Auth requirements
List which environment variables the plugin needs. This helps the AI give useful error messages — if auth fails, it can tell the user exactly what to set up. For OAuth plugins, include the login command (e.g., "run kazibee gmail login").
Common workflow
Describe the order of operations. Many APIs require a setup call first — like getting a workspace ID before listing projects. Without this, the AI will try to call methods with missing arguments.
Intent mapping
Map natural language phrases to method calls. When a user says "start a timer," the AI needs to know that means createTimeEntry(). This is the most impactful section — it directly determines whether the AI picks the right method.
Code call examples
Show runnable code snippets using await tools["plugin-name"].method(). Include realistic arguments and chained workflows (e.g., get user, then use their ID for the next call). The AI learns the exact calling pattern from these examples.
Output behavior
Tell the AI how to present results to the user. Should it show IDs? Format dates? Summarize long lists? Without this guidance, the AI might dump raw JSON or hide important information.
Bad vs. good: why specificity matters
The difference between a useful and useless llm.txt comes down to how specific you are. Compare these two approaches:
Bad: too vague
Tool: my-api
This tool connects to an API.
It has methods for reading and writing data.
Use the appropriate method for each request.
The AI has no idea what methods exist, what arguments they take, or what "appropriate" means. It will hallucinate method names and pass wrong arguments.
Good: specific and actionable
Tool: my-api
Package: my-api
Use this tool to manage customer records in the CRM.
Auth requirements
- CRM_API_KEY
Typical intents to map
- "find customer" -> searchCustomers(query, limit?)
- "add customer" -> createCustomer({ name, email })
- "update customer" -> updateCustomer(id, { ...fields })
Code call examples
const results = await tools["my-api"].searchCustomers("acme", 10);
const customer = await tools["my-api"].createCustomer({
name: "Jane Doe",
email: "jane@acme.com"
});
Output behavior
- Show customer name and email in results, not raw IDs.
- If search returns no results, say so explicitly.
- On create/update, confirm success with the customer name.
Now the AI knows exactly which methods to call, what arguments they expect, and how to present results. No guessing required.
Larger example: Gmail plugin
Plugins with many methods benefit from additional sections. The Gmail plugin includes a method selection policy (when the AI has multiple ways to do something) and defaults and limits (sane defaults when the user is vague):
Method selection policy
- For generic list intents, use listMessageSummaries(query?, maxResults?).
- Use listMessages() only when user explicitly asks for IDs-only output.
- Use getMessage(messageId) to read one specific message.
- Use replyToMessage(messageId, body) to reply in the same thread.
Defaults and limits
- If user gives no count, use maxResults = 20.
- If user asks for "all emails", use a safe cap of 50.
- Preserve user-provided filters exactly (from:, subject:, is:unread).
Output behavior
- Default to listMessageSummaries for generic "get my emails" requests.
- Do not show internal message IDs unless the user asks for them.
- For list outputs, prefer: from, subject, date, snippet.
- On auth errors, tell user to run: kazibee gmail login.
These extra sections prevent the AI from making common mistakes: using the wrong list method, returning too many results, or showing raw metadata instead of human-friendly summaries.
Writing tips
Write for someone who has never seen your API
The AI has no prior knowledge of your service. Explain what parameters mean, not just what they are called. "workspaceId (get this from getCurrentUser().activeWorkspace)" is much better than just "workspaceId".
Include the calling convention
Always use await tools["plugin-name"].method() in your examples. This is the exact pattern the AI needs to emit. If you write examples in a different format, the AI might get the syntax wrong.
Map user intent to methods, not just methods to descriptions
Users say "send an email," not "call sendMessage." The intent mapping section is what bridges the gap. Include as many natural phrases as you can think of for each method.
Specify error recovery
Tell the AI what to do when things go wrong. "On auth errors, tell user to run kazibee <plugin> login" is far more helpful than letting the AI make up its own error message.
Test it with kazibee llm
Run kazibee llm <plugin-name> to preview exactly what the AI will see. Read through it as if you were an AI with no context — would you know what to do?