{"id":50856061,"url":"https://github.com/moneymaster44444/local-buddy","last_synced_at":"2026-06-14T18:38:15.368Z","repository":{"id":361697774,"uuid":"1255305200","full_name":"moneymaster44444/local-buddy","owner":"moneymaster44444","description":"A local-first CLI AI agent for LM Studio: streaming chat plus sandboxed filesystem, shell, and web tools behind an approval gate.","archived":false,"fork":false,"pushed_at":"2026-06-01T02:29:12.000Z","size":146,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T18:38:14.266Z","etag":null,"topics":["ai-agent","cli","llm","lm-studio","local-llm","pydantic-ai","python","tool-calling"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moneymaster44444.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T17:03:03.000Z","updated_at":"2026-06-01T02:29:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moneymaster44444/local-buddy","commit_stats":null,"previous_names":["moneymaster44444/local-buddy"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/moneymaster44444/local-buddy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moneymaster44444%2Flocal-buddy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moneymaster44444%2Flocal-buddy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moneymaster44444%2Flocal-buddy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moneymaster44444%2Flocal-buddy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moneymaster44444","download_url":"https://codeload.github.com/moneymaster44444/local-buddy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moneymaster44444%2Flocal-buddy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34333806,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","cli","llm","lm-studio","local-llm","pydantic-ai","python","tool-calling"],"created_at":"2026-06-14T18:38:14.801Z","updated_at":"2026-06-14T18:38:15.354Z","avatar_url":"https://github.com/moneymaster44444.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LocalBuddy\n\nA local-first command-line AI agent that talks to models served by **LM Studio**\nover its OpenAI-compatible API. **Phases 1–4** are built: an interactive,\nstreaming chat REPL (model switching, reasoning/thinking display, rolling history\nsummarization); tool calling — sandboxed filesystem, shell, and web-fetch tools\ndriven through an agent loop, gated by an approval prompt for risky actions, with\na per-turn iteration cap; local RAG — ingest your documents, embed them via\nLM Studio into a LanceDB store, and let the agent retrieve from them with a\n`search_memory` tool; and durable sessions — every conversation is saved to\nSQLite, so `/sessions` and `/resume` reopen past chats with full context.\n\n## Requirements\n\n- Python 3.12+ (developed on 3.14)\n- [uv](https://docs.astral.sh/uv/)\n- **LM Studio** running its local server (Developer → Start Server) at\n  `http://localhost:1234/v1`, with **two models loaded**:\n  - a larger *brain* model for chat/reasoning (e.g. a Qwen3-class ~27–30B model)\n  - a small *utility* model for cheap summarization (e.g. Gemma 4 E4B)\n  - *(optional, for RAG)* an embedding model (e.g. `text-embedding-nomic-embed-text`)\n\nModel ids are **never hardcoded** — LocalBuddy reads them from `GET /v1/models`\nand lets you pick (or pin them via configuration).\n\n## Setup\n\n```bash\nuv sync\n```\n\n## Run\n\n```bash\nuv run localbuddy\n# equivalently:\nuv run python -m agent\n```\n\nOn first run (with no models pinned) LocalBuddy lists the models LM Studio\nreports and asks you to choose a brain and a utility model.\n\n## Commands\n\n| Command | Description |\n| --- | --- |\n| `/help` | Show commands |\n| `/model` | Show current brain \u0026 utility models and all available |\n| `/model brain [id\\|#]` | Switch the brain model (interactive picker if no id) |\n| `/model utility [id\\|#]` | Switch the utility model (interactive picker if no id) |\n| `/tools [reset\\|revoke \u003ctool\u003e]` | List tools \u0026 approvals; `revoke \u003ctool\u003e` downshifts one, `reset` clears all session auto-approvals |\n| `/ingest \u003cpath\u003e` | Add a text/Markdown file or folder to the knowledge base (RAG) |\n| `/remember [text]` | Save a note — or, with no text, a summary of this conversation — to memory |\n| `/memory` | Show knowledge base stats (chunks, sources, embedder) |\n| `/forget` | Clear the knowledge base |\n| `/sessions [delete \u003c#\\|id\u003e]` | List saved conversations (or delete one) |\n| `/resume [#\\|id]` | Resume a saved conversation (most recent if no id) |\n| `/clear` | Start a new conversation (the previous one stays saved) |\n| `/exit`, `/quit` | Quit |\n\nInput keys: **Enter** sends, **Alt+Enter** inserts a newline, **Ctrl+C** cancels\nthe current reply, **Ctrl+D** exits.\n\n## Tools \u0026 safety (Phase 2)\n\n\u003e ⚠️ **Experimental.** LocalBuddy can read/write files and run shell commands on\n\u003e your machine. Risky actions are gated behind an approval prompt, but the shell\n\u003e is a real shell — read each action before approving, and run it only on\n\u003e code/data you trust. No warranty; use at your own risk.\n\nThe brain model can call tools. Read-only tools run automatically; anything that\nchanges the world prompts you first:\n\n| Tool | Approval? |\n| --- | --- |\n| `read_file`, `list_dir` | none (read-only) |\n| `write_file`, `delete_path` | **required** |\n| `run_shell` | **required** |\n| `fetch_url` | **required** |\n\nWhen a risky tool is called the run **pauses** and shows you the exact action and\nits arguments. Answer **y** (once), **N** (deny — the model is told and adapts),\nor **a** (always allow this tool for the rest of the session). Grants can be\n**downshifted** any time: `/tools revoke \u003ctool\u003e` drops one back to prompting, and\n`/tools reset` clears them all. For the *sandboxed* tools (`write_file`,\n`delete_path`) **a** is\na single step. For `run_shell` and `fetch_url` — which reach **outside** the\nsandbox — choosing **a** requires a second, typed `yes` confirmation, so blanket\napproval of unrestricted tools is always deliberate (useful for longer\nautonomous runs, hard to grant by accident).\n\n- **Filesystem sandbox:** all `read_file`/`write_file`/`list_dir`/`delete_path`\n  paths are confined to `LOCALBUDDY_WORKSPACE_ROOT` (default `./workspace`,\n  auto-created). Paths that escape it are rejected.\n- **Shell:** runs in the workspace dir using the platform's native shell\n  (PowerShell on Windows by default; `$SHELL`/`/bin/sh` elsewhere). Note the shell\n  is a *real* shell — it is **not** path-confined; its only guard is the per-call\n  approval prompt, so read each command before approving.\n- **Iteration cap:** a turn is limited to `LOCALBUDDY_MAX_MODEL_REQUESTS` model\n  calls and `LOCALBUDDY_MAX_TOOL_CALLS` tool executions, as a runaway guard.\n\nReasoning models (e.g. Qwen3) stream their **thinking** dimmed before the answer;\nset `LOCALBUDDY_SHOW_THINKING=false` to hide it.\n\n## Knowledge base / RAG (Phase 3)\n\nGive LocalBuddy your own documents and let it retrieve from them:\n\n- **Ingest:** `/ingest \u003cpath\u003e` chunks a text/Markdown file (or every text file in\n  a folder), embeds each chunk via LM Studio's embedding model, and stores it in a\n  local **LanceDB** index under `data/lancedb/`. Re-ingesting a path replaces its\n  previous chunks. `/memory` shows stats; `/forget` clears the index. **Relative\n  paths resolve inside the workspace** (where the agent's `write_file` lands), so\n  `/ingest notes.txt` finds `workspace/notes.txt`; use an absolute path for files\n  elsewhere.\n- **Remember:** `/remember \u003ctext\u003e` saves a note; bare `/remember` summarizes the\n  *current conversation* (via the utility model) and saves that — so a future\n  session can recall the gist. Both are embedded and stored like any other entry,\n  tagged `note:\u003ctime\u003e` / `conversation:\u003ctime\u003e`. (This is long-term *recall* via\n  RAG; *resuming* a whole conversation verbatim is `/resume` — see Durable sessions.)\n- **Retrieve:** the agent has a read-only **`search_memory`** tool it calls when a\n  question might be answered by your documents — it embeds the query, pulls the top\n  matches, and grounds its answer in them (no approval needed; it's read-only).\n- **Embedding model:** resolved lazily — a configured id, else an auto-detected\n  `*embed*` model, else an interactive pick on your first `/ingest`. Nothing is\n  embedded (and no embedding model is loaded) until you ingest or the agent\n  searches, so RAG adds no startup or VRAM cost until used.\n\n## Durable sessions (Phase 4)\n\nEvery conversation is **saved to a local SQLite database** (`data/sessions.db`)\nafter each completed turn, so it survives `/exit`, Ctrl+C, or a crash. Messages\nare serialized with pydantic-ai's `ModelMessagesTypeAdapter`, so tool calls and\nthe full structure round-trip.\n\n- **`/sessions`** lists your saved conversations (newest first) with a short id,\n  timestamp, message count, and title. `/sessions delete \u003c#|id\u003e` removes one.\n- **`/resume \u003c#|id\u003e`** reopens a conversation with full context; bare `/resume`\n  reopens the most recent. You continue exactly where you left off.\n- **`/clear`** starts a *new* conversation — the previous one stays saved and\n  resumable.\n\nPartial/aborted turns are rolled back and never saved, so `/resume` always\nreturns you to the last *completed* turn. Set `LOCALBUDDY_PERSIST_SESSIONS=false`\nto disable. (Distinct from the knowledge base: `/resume` brings back a\n*conversation*; `search_memory` brings back *knowledge*.)\n\n## Configuration\n\nCopy `.env.example` to `.env` to override defaults (or set `LOCALBUDDY_*`\nenvironment variables). Notable settings:\n\n- `LOCALBUDDY_BASE_URL` — LM Studio endpoint (default `http://localhost:1234/v1`)\n- `LOCALBUDDY_BRAIN_MODEL_ID`, `LOCALBUDDY_UTILITY_MODEL_ID` — pin to skip the picker\n- `LOCALBUDDY_HISTORY_TOKEN_BUDGET` (default `6000`) — when the *estimated* token\n  size of the conversation exceeds this, the oldest turns are summarized by the\n  utility model and replaced with a compact summary turn\n- `LOCALBUDDY_KEEP_RECENT_TURNS` (default `4`) — recent turns always kept verbatim\n- `LOCALBUDDY_ENABLE_TOOLS` (default `true`) — set `false` for pure chat, no tools\n- `LOCALBUDDY_WORKSPACE_ROOT` (default `workspace`) — filesystem sandbox root\n- `LOCALBUDDY_WINDOWS_SHELL` (`powershell` | `cmd`, default `powershell`)\n- `LOCALBUDDY_SHELL_TIMEOUT` (default `30`), `LOCALBUDDY_WEBFETCH_TIMEOUT` (default `20`)\n- `LOCALBUDDY_MAX_MODEL_REQUESTS` (default `12`), `LOCALBUDDY_MAX_TOOL_CALLS` (default `24`)\n- `LOCALBUDDY_SHOW_THINKING` (default `true`) — stream reasoning-model thinking, dimmed\n- `LOCALBUDDY_ENABLE_MEMORY` (default `true`) — RAG: `search_memory` tool + `/ingest`\n- `LOCALBUDDY_EMBEDDER_MODEL_ID` — pin the embedding model (else auto/interactive)\n- `LOCALBUDDY_CHUNK_CHARS` (default `1200`), `LOCALBUDDY_CHUNK_OVERLAP` (default `200`)\n- `LOCALBUDDY_RAG_TOP_K` (default `5`) — passages returned per `search_memory` call\n- `LOCALBUDDY_PERSIST_SESSIONS` (default `true`) — save/resume conversations via SQLite\n\nToken size is **estimated** with a `chars / 4` heuristic\n(`LOCALBUDDY_CHARS_PER_TOKEN`), so no model-specific tokenizer dependency is\nneeded.\n\n## How it works\n\n```\nagent/\n  config.py     # pydantic-settings configuration\n  llm.py        # shared AsyncOpenAI client → LM Studio; model discovery + agent/model builders (UI-free)\n  state.py      # Conversation (pydantic-ai messages) + rolling summarization\n  loop.py       # the agent step loop: stream → tools → approval → resume, with the iteration cap\n  checkpoint.py # SQLite session persistence: save / list / resume conversations (Phase 4)\n  repl.py       # the REPL, commands, model picker, approval UI, bootstrap\n  tools/        # filesystem, shell, webfetch, search_memory tools + the approval gate\n  memory/       # embeddings (LM Studio), LanceDB store, ingest, retrieval (Phase 3)\n  __main__.py   # `python -m agent` entry point\ndata/           # repl history + LanceDB index + sessions.db (gitignored)\nworkspace/      # filesystem sandbox for tools (gitignored)\n```\n\nThe agent and tool-calling loop are built on **pydantic-ai**, connected to LM\nStudio via its OpenAI-compatible provider. We depend on\n`pydantic-ai-slim[openai]` rather than the full `pydantic-ai` meta-package: it's\nthe same library but pulls only the OpenAI-compatible provider this project\nneeds, avoiding ~8 unused cloud-provider SDKs.\n\nThe brain model is supplied per request (`agent.iter(..., model=...)`), so\nswitching models at runtime needs no agent rebuild. The system prompt is set as\nagent *instructions*, which keeps it out of the stored message history (and thus\nout of summarization) while always being applied.\n\nA user turn runs through `agent.iter()` (in `loop.py`): one code path streams the\nanswer, executes read-only tools inline, and — when the model calls a tool marked\n`requires_approval=True` — **pauses** the run (pydantic-ai's `DeferredToolRequests`\nhuman-in-the-loop mechanism), asks via the approval gate, then **resumes** with the\nresults. The per-turn iteration cap is enforced with `UsageLimits`.\n\nAfter each completed turn the conversation is serialized and upserted into SQLite\n(`checkpoint.py`); `/resume` deserializes a saved session back into the live\nconversation. Because messages stay in pydantic-ai's own format, resumed sessions\nkeep tool calls, summaries, and everything else intact.\n\n## Manual test checklist\n\n1. `uv sync`; ensure LM Studio is serving two models.\n2. `uv run localbuddy` → pick a brain and a utility model when prompted.\n3. Ask a question → reasoning streams dimmed (if any), then the reply streams in.\n4. `/model` → table shows both roles and all models; `/model utility` → re-pick.\n5. `/tools` → lists the tools and their approval status, plus the workspace path.\n6. **Read tool (no approval):** ask \"list the files in your workspace\" → it runs\n   `list_dir` inline without prompting.\n7. **Write tool (approval):** ask \"create notes.txt with 'hello'\" → you get an\n   approval panel showing the path \u0026 content; **y** writes it (check\n   `workspace/notes.txt`), **N** declines and the model adapts.\n8. **Shell tool:** ask \"run `echo hi` in the shell\" → approval panel shows the\n   command; approve and see the output.\n9. **Iteration cap:** lower `LOCALBUDDY_MAX_MODEL_REQUESTS` and give a multi-step\n   task → the turn stops with an \"iteration cap reached\" notice.\n10. **RAG:** create a text file, `/ingest \u003cpath\u003e` it (approve the embedder pick if\n    asked), then `/memory` shows the chunk count and source. Ask a question whose\n    answer is in that file → the agent calls `search_memory` (you'll see a 🔧 line)\n    and grounds its answer in the retrieved passage. `/forget` clears the index.\n11. **Durable sessions:** have a short chat, tell it something specific, then\n    `/exit`. Relaunch → the startup line shows N saved conversation(s); `/sessions`\n    lists them; `/resume` (or `/resume \u003c#\u003e`) reopens the latest → ask it to recall\n    what you told it and it answers from the restored context.\n12. **New vs. resume:** `/clear` starts a fresh conversation (the old one stays in\n    `/sessions`); `/sessions delete \u003c#\u003e` removes one.\n13. Force summarization: set `LOCALBUDDY_HISTORY_TOKEN_BUDGET=300`, then hold a\n    short conversation. Once the budget is exceeded you'll see\n    `↳ summarized N older message(s)…` and the context stays bounded.\n14. `/exit` quits; stop the LM Studio server and start LocalBuddy → you get a clear\n    connection error rather than a traceback.\n\n## Roadmap\n\n- **Phase 1 ✓** — streaming chat REPL, model switching, rolling summarization\n- **Phase 2 ✓** — filesystem / shell / web-fetch tools, the agent loop, an\n  approval gate for risky calls, and a per-turn iteration cap\n- **Phase 3 ✓** — local RAG: chunk → embed (via LM Studio) → LanceDB, retrieved via a `search_memory` tool + `/ingest`\n- **Phase 4 ✓** — durable sessions persisted to SQLite, reopened with `/sessions` + `/resume`\n- **Deferred** — Phase 5 (daemon + scheduler), Phase 6 (MCP integrations)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoneymaster44444%2Flocal-buddy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoneymaster44444%2Flocal-buddy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoneymaster44444%2Flocal-buddy/lists"}