This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
SlothVault is a local-first personal RAG utility built for anyone too tired or busy to manually sort, tag, and organize digital clutter.
Every day, developers and researchers accumulate messy context fragments:
Most productivity guides advise spending hours building complex databases in Notion or Obsidian. SlothVault takes the lazy approach: dump your unorganized notes into a local vault and let an open-weight agent fetch what you need in seconds.
.txt files on the fly.retrieve_from_vault and add_to_vault).
import json
import torch
import numpy as np
import gradio as gr
from transformers import pipeline
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
device = "cuda" if torch.cuda.is_available() else "cpu"
llm_pipeline = pipeline(
"text-generation",
model="Qwen/Qwen2.5-1.5B-Instruct",
torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
device_map="auto" if torch.cuda.is_available() else None
)
vault_docs = [
"WiFi Password for Home Lab is 'lazy_sloth_42'. Do not share externally.",
"Invoice #8841 from Cloud hosting was $45.20, due on November 5th.",
"Doctor appointment rescheduled to Friday at 4:30 PM with Dr. Sharma.",
"Project roadmap milestone 2 requires TabPFN model benchmarking by next Tuesday."
]
vault_embeddings = embed_model.encode(vault_docs)
def mcp_retrieve_from_vault(query: str, top_k: int = 2):
global vault_docs, vault_embeddings
if not vault_docs:
return []
q_vec = embed_model.encode([query])
scores = cosine_similarity(q_vec, vault_embeddings)[0]
top_indices = np.argsort(scores)[::-1][:top_k]
return [{"text": vault_docs[i], "similarity_score": round(float(scores[i]), 3)} for i in top_indices]
def mcp_add_to_vault(new_text: str):
global vault_docs, vault_embeddings
cleaned = new_text.strip()
if not cleaned:
return "Empty note."
vault_docs.append(cleaned)
new_embedding = embed_model.encode([cleaned])
vault_embeddings = np.vstack([vault_embeddings, new_embedding])
return f"Added to vault. Total items: {len(vault_docs)}"
SYSTEM_PROMPT = (
"You are SlothVault. The user is exhausted. You have executed the MCP tool "
"'retrieve_from_vault' and received matching records. "
"Give the exact answer in 1-2 punchy sentences. "
"End with a short sloth remark like 'Now go back to resting. 🦥'."
)
def sloth_agent_search(query: str):
if not query.strip():
return "Ask something you're too lazy to search for!"
retrieved = mcp_retrieve_from_vault(query, top_k=2)
context_str = "\n".join([f"- {r['text']} (Match: {r['similarity_score']})" for r in retrieved])
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Query: {query}\n\nContext:\n{context_str}"}
]
res = llm_pipeline(messages, max_new_tokens=120, temperature=0.2)
answer = res[0]["generated_text"][-1]["content"].strip()
trace = (
f"```
{% endraw %}
json\n"
f"// MCP Tool Execution Trace\n"
f'{{"tool": "retrieve_from_vault", "query": "{query}", "matches": {len(retrieved)}}}\n'
f"
{% raw %}
\n\n"
f"**Retrieved Context:**\n{context_str}\n\n"
f"---\n"
f"### Sloth Answer\n{answer}"
)
return trace
def handle_upload(file_obj, raw_text):
count = 0
if file_obj is not None:
with open(file_obj.name, "r", encoding="utf-8", errors="ignore") as f:
for line in f.readlines():
if line.strip():
mcp_add_to_vault(line.strip())
count += 1
if raw_text and raw_text.strip():
mcp_add_to_vault(raw_text.strip())
count += 1
return f"Successfully ingested {count} item(s) into vault."
with gr.Blocks(theme=gr.themes.Soft(), title="SlothVault") as demo:
gr.Markdown("# SlothVault: Zero-Effort Local MCP + RAG Agent")
gr.Markdown("Stop organizing notes manually. Dump raw clutter and let an open-weight agent retrieve it.")
with gr.Tab(" Ask the Sloth Agent"):
query_in = gr.Textbox(label="Ask anything:", value="What is my home lab wifi password and when is the cloud bill due?")
search_btn = gr.Button(" Find It For Me", variant="primary")
output_box = gr.Markdown()
search_btn.click(fn=sloth_agent_search, inputs=query_in, outputs=output_box)
with gr.Tab(" Dump More Clutter (Add Notes)"):
note_in = gr.Textbox(label="Paste note or snippet:", placeholder="e.g., Gate code is 8842")
file_in = gr.File(label="Upload .txt file", file_types=[".txt"])
add_btn = gr.Button(" Ingest Without Organizing")
status_box = gr.Markdown()
add_btn.click(fn=handle_upload, inputs=[file_in, note_in], outputs=status_box)
demo.launch(share=True)
How I Built It
SlothVault combines open-weight models, dense semantic retrieval, and the Model Context Protocol (MCP):
Standardized MCP Tool Contracts: Instead of building an ad-hoc search script, the data operations implement retrieve_from_vault and add_to_vault tool definitions, ensuring compatibility with standard agent frameworks.
Fast Local Vector Search: sentence-transformers/all-MiniLM-L6-v2 embeds text into 384-dimensional vectors. Cosine similarity matching retrieves the top-ranked context in milliseconds without a heavy external vector database.
Local Open-Weight Reasoning: Qwen/Qwen2.5-1.5B-Instruct synthesizes the retrieved chunks on-device, extracting precise facts and avoiding generative hallucinations.
Why Does Open Innovation Matter?
Absolute Privacy: Personal notes, Wi-Fi credentials, medical reminders, and invoices never leave the machine. Running inference locally keeps sensitive personal data off third-party servers.
Zero Operating Cost: Powered by open weights, the agent runs entirely free of token limits, monthly subscriptions, or cloud API paywalls.
Offline Reliability: Because the models run on-device, the agent functions normally on a laptop even without an internet connection.
Prize Categories
Top Prize: Slow and Steady ($250 USD)