A small, hackable LLM agent framework. mKBI accepts natural language requests, routes them through a two-stage LLM pipeline, optionally runs static analysis on the output, and executes the result in a subprocess, returning the output to the initial caller. Skills are simple and easy to define: pick an execution method, give some examples, and you're all set.
mKBI runs a two-model execution chain for every task:
- Interpreter — receives the user's natural language request along with the skill's interpreter history (system prompt and few-shot examples). Produces code that addresses the request.
- Fabricator — receives a combined
[KCR]prompt containing the original request and the Interpreter's output. Cleans, validates, and formats the code into a directly executable script. Prepends the appropriate shebang. - Static analysis (optional) — runs a configured linter on the generated script. If it reports errors, execution is aborted.
- Execution — runs the script in a subprocess with a configurable timeout. The process runs in its own session group so it can be cleanly killed on timeout.
mKBI/
├── mKBI.py # Core service: LLMService class and execution pipeline
├── mKBI_api.py # FastAPI wrapper exposing the pipeline as an HTTP service
├── mKBI.toml # Configuration file
└── skills/
├── bash.json # Bash skill (default)
└── python.json # Python skill
Python 3.11 or later is required.
pip install openai fastapi uvicorn python-dotenvshellcheck is required for static analysis of bash skills. Install it via your system package manager (apt install shellcheck, brew install shellcheck, etc.). If it is not present on PATH, the analysis step is skipped with a warning and execution continues.
Clone the repo and place your API key either in mKBI.toml or as an environment variable:
git clone https://github.com/cbigger/mKBI.git
cd mKBI
export LLM_API_KEY=your_key_hereAll configuration lives in mKBI.toml. The path can be overridden via the MKBI_CONFIG environment variable.
[api]
key = "YOUR_API_KEY_HERE" # or leave as placeholder and use LLM_API_KEY env var
base_url = "https://openrouter.ai/api/v1"
[skills]
dir = "skills" # path to the skills directory, relative to cwd
[service]
model = "openrouter/auto" # model string passed to the API
default_skill = "bash" # skill used by /execute and /interpret (backward compat)
[interpreter]
temperature = 0.7
top_p = 1.0
context_length = 4000
[fabricator]
temperature = 1.0
top_p = 0.4
context_length = 16000
[execution]
timeout = 30 # seconds before the subprocess is killedAPI key resolution order:
[api] keyinmKBI.toml, if it is not the placeholder stringLLM_API_KEYenvironment variableLLM_API_KEYin a.envfile in the working directory- Fatal error
The base_url can point to any OpenAI-compatible endpoint. OpenRouter is the default, but you can point it directly at the OpenAI API, a local Ollama instance, or any other compatible provider.
# Run a task using the default skill
python3 mKBI.py "list all running processes sorted by memory"
# Run a task using a specific skill
python3 mKBI.py --skill python "what is today's date and time"
# Return only stdout (useful for piping)
python3 mKBI.py --output-only "show disk usage for the home directory"
# Both flags together
python3 mKBI.py --skill python --output-only "calculate the first 20 fibonacci numbers"The full result dict is printed by default. With --output-only, only stdout from the executed script is printed.
A skill is a single JSON file in the skills directory. The filename stem is the skill name (bash.json -> bash, python.json -> python). All skills are loaded at startup. New skills are picked up by restarting the process or calling POST /skills/reload via the API.
{
"meta": {
"executor": "bash",
"file_extension": ".sh",
"static_analysis": "shellcheck"
},
"interpreter": [
{ "role": "system", "content": "..." },
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": "..." }
],
"fabricator": [
{ "role": "system", "content": "..." },
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": "..." }
]
}| Field | Type | Description |
|---|---|---|
executor |
string | The runtime used to execute the generated script. See supported executors below. |
file_extension |
string | Extension for the temp file written before execution (e.g. .sh, .py). |
static_analysis |
string or null | Linter to run before execution. null skips analysis. |
Both interpreter and fabricator arrays are prepended to every API call made by their respective stage. They serve two purposes:
- The
systemrole message defines the agent's persona and constraints for that skill. - The subsequent
user/assistantpairs are few-shot examples that teach the model the expected input/output format.
The Fabricator receives a combined prompt of the form:
<original user request> [KCR] <interpreter output>
The [KCR] tag (KBI Code Requisition) is the handoff signal. The Fabricator's examples should use this same format in their user messages so the model learns to expect it.
- Create a new file in the
skills/directory, e.g.skills/node.json. - Fill in the
metablock with the appropriate executor, extension, and analysis tool (ornull). - Write a
systemmessage for the Interpreter that defines its language specialty and any constraints (e.g. "use only the standard library"). - Write a
systemmessage for the Fabricator that instructs it to output a clean, directly executable script with the correct shebang. Explicitly tell it to produce no markdown fencing or explanation. - Add at least four or five
user/assistantfew-shot pairs to each history array. Good examples cover the range of tasks the skill is likely to handle. The Fabricator examples should show improvement over the raw Interpreter output — correcting style, adding error handling, formatting output.
A minimal new skill looks like this:
{
"meta": {
"executor": "node",
"file_extension": ".js",
"static_analysis": null
},
"interpreter": [
{
"role": "system",
"content": "You are a KBI Interpreter unit specializing in Node.js. When a user requests a task, respond with Node.js code using only built-in modules. You are not responsible for returning code output to the user directly."
}
],
"fabricator": [
{
"role": "system",
"content": "You are a KBI Fabricator unit. You receive [KCR] prompts and produce clean, executable Node.js scripts. Output only the raw script with no markdown fencing. Add console.log() for plain text requests."
},
{
"role": "user",
"content": "What Node.js version is running? [KCR] console.log(process.version)"
},
{
"role": "assistant",
"content": "console.log('Node.js version:', process.version);"
}
]
}The following executor names are recognised out of the box:
| Name | Command |
|---|---|
bash |
bash {script} |
python3 |
python3 {script} |
python |
python {script} |
node |
node {script} |
ruby |
ruby {script} |
perl |
perl {script} |
To add a new executor, add an entry to _EXECUTOR_CMDS in mKBI.py:
_EXECUTOR_CMDS: dict[str, list[str]] = {
...
"deno": ["deno", "run", "{script}"],
}The following analysis tools are recognised:
| Name | Command |
|---|---|
shellcheck |
shellcheck {script} |
If the named binary is not found on PATH, the step is skipped with a warning and execution proceeds. If analysis runs and reports errors, execution is aborted and the error output is returned in the result.
To add a new analysis tool, add an entry to _STATIC_ANALYSIS_CMDS in mKBI.py:
_STATIC_ANALYSIS_CMDS: dict[str, list[str]] = {
...
"pyflakes": ["pyflakes", "{script}"],
}uvicorn mKBI_api:app --host 0.0.0.0 --port 8000Or directly:
python3 mKBI_api.pyThe host and port can be configured via environment variables:
| Variable | Default | Description |
|---|---|---|
MKBI_CONFIG |
mKBI.toml |
Path to the config file |
MKBI_HOST |
0.0.0.0 |
Bind address |
MKBI_PORT |
8000 |
Bind port |
MKBI_TOKEN |
(unset) | Bearer token for auth. If unset, auth is disabled. |
Interactive API docs are available at http://localhost:8000/docs once the server is running.
If MKBI_TOKEN is set, all POST endpoints require a bearer token:
Authorization: Bearer <your_token>
GET endpoints (/health, /skills) do not require authentication.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Liveness check. Returns model name, default skill, and uptime. |
| GET | /skills |
No | List all loaded skills with their executor and analysis tool. |
| POST | /skills/reload |
Yes | Rescan the skills directory and reload all skill definitions. |
| POST | /skills/{skill}/execute |
Yes | Run the full pipeline for a named skill. |
| POST | /skills/{skill}/interpret |
Yes | Run the Interpreter stage only for a named skill. No execution. |
| POST | /execute |
Yes | Full pipeline using default_skill from config. |
| POST | /interpret |
Yes | Interpreter only using default_skill from config. |
POST /skills/{skill}/execute and POST /execute
Request body:
{
"request": "list files in the current directory sorted by size",
"output_only": false
}output_only: true returns a stripped response containing only stdout. Useful when the caller only needs the script's output and not pipeline metadata.
Full response (output_only: false):
{
"skill": "bash",
"interpreter_response": "ls -lS .",
"fabricator_response": "#!/bin/bash\nls -lhS .",
"script": "#!/bin/bash\nls -lhS .",
"shellcheck_passed": true,
"shellcheck_output": "",
"execution": {
"stdout": "total 48\n-rw-r--r-- 1 user user 12400 ...",
"stderr": "",
"returncode": 0,
"timed_out": false
},
"error": null,
"elapsed_seconds": 2.341
}Output-only response (output_only: true):
{
"output": "total 48\n-rw-r--r-- 1 user user 12400 ...",
"elapsed_seconds": 2.341
}POST /skills/{skill}/interpret and POST /interpret
Request body:
{
"request": "how do I list hidden files?"
}Response:
{
"response": "ls -a ~/",
"elapsed_seconds": 0.812
}GET /skills
Response:
[
{ "name": "bash", "executor": "bash", "analysis": "shellcheck" },
{ "name": "python", "executor": "python3", "analysis": null }
]GET /health
Response:
{
"status": "ok",
"model": "openrouter/auto",
"default_skill": "bash",
"uptime_seconds": 142.5
}# Health check
curl http://localhost:8000/health
# List skills
curl http://localhost:8000/skills
# Execute a bash task
curl -X POST http://localhost:8000/execute \
-H "Content-Type: application/json" \
-d '{"request": "show disk usage for the home directory"}'
# Execute using the python skill explicitly
curl -X POST http://localhost:8000/skills/python/execute \
-H "Content-Type: application/json" \
-d '{"request": "print the first 10 prime numbers"}'
# Get only stdout
curl -X POST http://localhost:8000/skills/python/execute \
-H "Content-Type: application/json" \
-d '{"request": "what is todays date", "output_only": true}'
# Interpret only (no execution)
curl -X POST http://localhost:8000/interpret \
-H "Content-Type: application/json" \
-d '{"request": "check if nginx is running"}'
# Reload skills after adding a new skill file (with auth enabled)
curl -X POST http://localhost:8000/skills/reload \
-H "Authorization: Bearer your_token_here"