Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit 9f3ad42

Browse files
committed
feat: Release v1.7.0 - Cerebras Acceleration, Memory Bank, and Agent Delegation
- Providers: Added native Cerebras Inference support with auto-configured base URL and Llama 3.1 pricing. - Memory: Implemented persistent semantic Memory Bank using ChromaDB and Sentence Transformers. - Tools: Added SaveMemoryTool and SearchMemoryTool for long-term context retention. - Agents: Formalized delegation workflow with DelegateToAgentTool and sub-agent context management. - Fixed: Resolved erroneous await on action_quit in TUI. - Infrastructure: Updated ROADMAP.md and bumped version to 1.7.0. - Dependencies: Added chromadb and sentence-transformers.
1 parent 1d63c50 commit 9f3ad42

18 files changed

Lines changed: 428 additions & 73 deletions

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,24 @@
44

55
![Plexir UI](assets/image.png)
66

7-
[![Version](https://img.shields.io/badge/version-1.6.0-blue.svg)](https://github.com/pomilon/plexir)
7+
[![Version](https://img.shields.io/badge/version-1.7.0-blue.svg)](https://github.com/pomilon/plexir)
88
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
99

1010
---
1111

1212
## 🚀 Features
1313

14-
- **Multi-Provider Failover**: Seamlessly switch between Gemini, Groq, and OpenAI-compatible APIs. If one model hits a quota, Plexir automatically fails over to the next in your priority list.
14+
- **Multi-Provider Failover**: Seamlessly switch between Gemini, Groq, Cerebras, and OpenAI-compatible APIs. If one model hits a quota, Plexir automatically fails over to the next in your priority list.
1515
- **Economics & Metrics**: Real-time **Token Tracking** and **Cost Estimation** in the sidebar. Set a session budget via `/config budget` to prevent runaway costs.
1616
- **Advanced Reasoning Support**: Automatically filters model "thinking" blocks into collapsible widgets and provides a **Live Status Spinner** during reasoning.
17-
- **Coherent Memory**: **Rolling Summarization** automatically condenses long histories, while **Message Pinning** (`/session pin`) ensures critical context is never lost.
17+
- **Coherent Memory**:
18+
- **Persistent Memory Bank**: Semantic storage (`chromadb`) for long-term facts using `/memory save`.
19+
- **Rolling Summarization**: Automatically condenses long histories.
20+
- **Message Pinning**: `/session pin` ensures critical context is never lost.
1821
- **Persistent Docker Sandbox**: Launch with `--sandbox` to give the AI its own persistent Linux "computer." All tools (file system, git, shell) are automatically redirected inside the container.
1922
- **Deep MCP Integration**: Fully supports **Model Context Protocol (MCP)**, including dynamic discovery of tools, **Resources**, **Resource Templates**, and **Prompts** from MCP servers.
2023
- **Smart Agent Capabilities**:
24+
- **Delegation**: `delegate_to_agent` allows spawning specialized sub-agents for complex tasks.
2125
- **RAG & Context**: `codebase_search` allows natural language queries across your codebase. `get_definitions` quickly maps file structures.
2226
- **Planning**: Built-in `scratchpad` memory for long-term planning and note-taking.
2327
- **Visual Safety**: Critical actions like writing files show a **Rich Visual Diff** (Red/Green) in the confirmation modal before execution.

ROADMAP.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,30 @@ We have successfully implemented the core foundation and advanced agentic capabi
8787

8888
---
8989

90+
## ✅ Completed Milestones (v1.6)
91+
92+
### 🚀 Core Architecture
93+
- [x] **Provider Abstraction**: Refactored `LLMProvider` to support `gemini`, `openai`, `groq`, and `ollama` seamlessly.
94+
- [x] **Thinking Blocks**: Added support for `<think>` tags to visualize reasoning chains in the TUI.
95+
- [x] **Config Manager**: Centralized configuration with secure secret handling (keyring support).
96+
97+
### 🛠️ Developer Experience
98+
- [x] **Sandbox Integration**: Docker-based sandboxing for safe code execution.
99+
- [x] **MCP Support**: Full integration with Model Context Protocol for extensible tools.
100+
101+
---
102+
103+
## ✅ Completed Milestones (v1.7)
104+
105+
### ⚡ Inference Acceleration
106+
- [x] **Cerebras Support**: Native integration for Cerebras Inference API (Llama 3.1 8B/70B) for ultra-fast generation.
107+
108+
### 🧠 Advanced Capabilities
109+
- [x] **Multi-Agent Delegation**: Formalized the `delegate_to_agent` workflow with dedicated sub-agent contexts.
110+
- [x] **Memory Bank**: Persistent long-term memory using vector stores (Chroma/Qdrant).
111+
112+
---
113+
90114
## 🔮 Long Term Vision (v2.0)
91115

92116
### 1. True "IDE-Like" UI

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Settings are stored in JSON format at `~/.plexir/config.json`. This file is auto
1010

1111
- **`gemini`**: Google's Gemini models. Supports **API Key** (AI Studio) and **OAuth** (Vertex AI/Standalone).
1212
- **`groq`**: Ultra-fast inference for Llama 3 and Mistral models. Requires a Groq API key.
13+
- **`cerebras`**: High-performance inference provider (OpenAI-compatible). Requires a Cerebras API key.
1314
- **`openai`**: Supports official OpenAI models or any OpenAI-compatible API (like local Ollama instances).
1415

1516
## Authentication Modes (`auth_mode`)

docs/memory.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

33
Plexir uses advanced techniques to manage long-running conversations, ensuring the model remains coherent even as the history grows.
44

5+
## 🧠 Persistent Memory Bank (New in v1.7)
6+
7+
Plexir now includes a **Long-Term Memory** system powered by `chromadb`. This allows the agent to store and recall specific facts across different sessions.
8+
9+
### Features
10+
- **Semantic Storage**: Memories are stored as embeddings, meaning the agent can find them even if the exact keywords don't match (e.g., searching for "database credentials" finds "db password").
11+
- **Session Persistence**: Memories persist even after you close Plexir. They are stored in `~/.plexir/memory`.
12+
- **Tools**:
13+
- `save_memory`: The agent uses this to store explicit user facts (e.g., "The user prefers Python over C++").
14+
- `search_memory`: The agent uses this to recall information when needed.
15+
16+
### Usage
17+
You can prompt the agent to remember things directly:
18+
> "Remember that my API keys are stored in .env.local"
19+
20+
Or ask it to recall:
21+
> "Where did I say my keys were?"
22+
523
## 🔄 Rolling Summarization
624

725
When a conversation history becomes too large (exceeding 40 messages), Plexir automatically triggers **Rolling Summarization**.
@@ -28,4 +46,4 @@ Use the `/session pin` command followed by the message number (visible in the hi
2846

2947
## 🧠 Distillation (Failover)
3048

31-
During a **Provider Failover** (e.g., Gemini Primary hitting a quota), Plexir uses a "Distillation" process to transfer only the most essential recent context to the backup provider. This ensures a smooth transition with minimal latency and token waste.
49+
During a **Provider Failover** (e.g., Gemini Primary hitting a quota), Plexir uses a "Distillation" process to transfer only the most essential recent context to the backup provider. This ensures a smooth transition with minimal latency and token waste.

docs/tools.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ Plexir agents are equipped with a powerful set of tools to interact with the sys
2929

3030
| Tool | Description | Critical? |
3131
| :--- | :--- | :--- |
32+
| `delegate_to_agent` | Spawns a specialized sub-agent (e.g., `researcher`) to handle complex sub-tasks autonomously. | No |
33+
| `save_memory` | Saves a specific fact or piece of information to long-term storage (`chromadb`). | No |
34+
| `search_memory` | Retrieves relevant memories based on a semantic query. | No |
3235
| `codebase_search` | Semantically searches code using natural language keywords. | No |
3336
| `scratchpad` | Reads/Writes/Clears a persistent memory file for planning. | No |
3437

plexir/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.6.0"
1+
__version__ = "1.7.0"

plexir/core/commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async def process(self, text: str) -> Optional[str]:
7474
await self.app.action_reload_providers()
7575
return "Providers reloaded from config."
7676
elif cmd in ("/quit", "/exit"):
77-
await self.app.action_quit()
77+
self.app.action_quit()
7878
return "Exiting..."
7979
else:
8080
return f"Unknown command: {cmd}. Type /help for list."

plexir/core/config_manager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def store_secret(username: str, secret: str):
5252
class ProviderConfig(BaseModel):
5353
"""Configuration for an individual LLM provider."""
5454
name: str = Field(..., description="Unique name for the provider.")
55-
type: str = Field(..., description="Type: gemini, openai, groq, ollama, mcp.")
55+
type: str = Field(..., description="Type: gemini, openai, groq, ollama, cerebras, mcp.")
5656
api_key: Optional[str] = None
5757
model_name: str
5858
base_url: Optional[str] = None
@@ -114,6 +114,10 @@ class AppConfig(BaseModel):
114114
"deepseek-v3": (0.27, 1.10),
115115
"deepseek-reasoner": (0.55, 2.19),
116116
"llama-3.3-70b-versatile": (0.59, 0.79),
117+
118+
# --- Cerebras Inference ---
119+
"llama3.1-8b": (0.10, 0.10),
120+
"llama3.1-70b": (0.60, 0.60),
117121
},
118122
description="Pricing map: model -> (prompt_price, completion_price) per 1M tokens."
119123
)

plexir/core/memory.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
Persistent Memory Bank for Plexir using ChromaDB.
3+
"""
4+
5+
import os
6+
import logging
7+
import uuid
8+
from typing import List, Dict, Any, Optional
9+
10+
try:
11+
import chromadb
12+
from chromadb.config import Settings
13+
from sentence_transformers import SentenceTransformer
14+
HAS_MEMORY_DEPS = True
15+
except ImportError:
16+
HAS_MEMORY_DEPS = False
17+
18+
logger = logging.getLogger(__name__)
19+
20+
MEMORY_DIR = os.path.expanduser("~/.plexir/memory")
21+
22+
class MemoryBank:
23+
_instance = None
24+
25+
def __new__(cls):
26+
if cls._instance is None:
27+
cls._instance = super(MemoryBank, cls).__new__(cls)
28+
cls._instance.initialized = False
29+
return cls._instance
30+
31+
def __init__(self):
32+
if self.initialized:
33+
return
34+
35+
if not HAS_MEMORY_DEPS:
36+
logger.warning("MemoryBank dependencies (chromadb, sentence-transformers) not found. Memory features disabled.")
37+
self.initialized = False
38+
return
39+
40+
os.makedirs(MEMORY_DIR, exist_ok=True)
41+
42+
try:
43+
self.client = chromadb.PersistentClient(path=MEMORY_DIR)
44+
45+
# Use a lightweight model for local embeddings
46+
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
47+
48+
self.collection = self.client.get_or_create_collection(
49+
name="plexir_memory",
50+
metadata={"hnsw:space": "cosine"}
51+
)
52+
self.initialized = True
53+
logger.info("MemoryBank initialized with ChromaDB.")
54+
except Exception as e:
55+
logger.error(f"Failed to initialize MemoryBank: {e}")
56+
self.initialized = False
57+
58+
def add(self, text: str, metadata: Dict[str, Any] = None) -> str:
59+
if not self.initialized:
60+
return "MemoryBank not initialized."
61+
62+
try:
63+
doc_id = str(uuid.uuid4())
64+
embedding = self.embedder.encode(text).tolist()
65+
66+
self.collection.add(
67+
documents=[text],
68+
embeddings=[embedding],
69+
metadatas=[metadata or {}],
70+
ids=[doc_id]
71+
)
72+
return f"Memory saved (ID: {doc_id})"
73+
except Exception as e:
74+
logger.error(f"Failed to add memory: {e}")
75+
return f"Error saving memory: {e}"
76+
77+
def search(self, query: str, n_results: int = 5) -> List[Dict[str, Any]]:
78+
if not self.initialized:
79+
return []
80+
81+
try:
82+
query_embedding = self.embedder.encode(query).tolist()
83+
84+
results = self.collection.query(
85+
query_embeddings=[query_embedding],
86+
n_results=n_results
87+
)
88+
89+
# Flatten results structure
90+
documents = results['documents'][0]
91+
metadatas = results['metadatas'][0]
92+
ids = results['ids'][0]
93+
distances = results['distances'][0]
94+
95+
formatted_results = []
96+
for i in range(len(documents)):
97+
formatted_results.append({
98+
"id": ids[i],
99+
"content": documents[i],
100+
"metadata": metadatas[i],
101+
"score": 1 - distances[i] # Convert distance to similarity score
102+
})
103+
104+
return formatted_results
105+
except Exception as e:
106+
logger.error(f"Memory search failed: {e}")
107+
return []
108+
109+
def delete(self, doc_id: str) -> str:
110+
if not self.initialized:
111+
return "MemoryBank not initialized."
112+
try:
113+
self.collection.delete(ids=[doc_id])
114+
return f"Memory {doc_id} deleted."
115+
except Exception as e:
116+
return f"Error deleting memory: {e}"

plexir/core/providers.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,17 @@ def __init__(self, config: ProviderConfig, tools: ToolRegistry):
254254
self.model_name = config.model_name
255255
api_key = config.get_api_key() or "MISSING_KEY"
256256

257-
if config.type == "groq" and not config.base_url:
257+
base_url = config.base_url
258+
if config.type == "groq" and not base_url:
258259
self.client = AsyncGroq(api_key=api_key)
259-
else:
260-
self.client = AsyncOpenAI(
261-
api_key=api_key,
262-
base_url=config.base_url or "https://api.openai.com/v1"
263-
)
260+
return
261+
elif config.type == "cerebras" and not base_url:
262+
base_url = "https://api.cerebras.ai/v1"
263+
264+
self.client = AsyncOpenAI(
265+
api_key=api_key,
266+
base_url=base_url or "https://api.openai.com/v1"
267+
)
264268

265269
async def generate(
266270
self,
@@ -330,14 +334,19 @@ async def generate(
330334
openai_tools = self.tools.to_openai_toolbox()
331335

332336
try:
333-
stream = await self.client.chat.completions.create(
334-
messages=messages,
335-
model=self.model_name,
336-
tools=openai_tools if openai_tools else None,
337-
tool_choice="auto" if openai_tools else None,
338-
stream=True,
339-
stream_options={"include_usage": True}
340-
)
337+
create_params = {
338+
"messages": messages,
339+
"model": self.model_name,
340+
"tools": openai_tools if openai_tools else None,
341+
"tool_choice": "auto" if openai_tools else None,
342+
"stream": True,
343+
}
344+
345+
# Only OpenAI (and possibly others) support stream_options for usage
346+
if self.config.type == "openai":
347+
create_params["stream_options"] = {"include_usage": True}
348+
349+
stream = await self.client.chat.completions.create(**create_params)
341350

342351
tool_call_accumulator = {}
343352

@@ -452,14 +461,19 @@ async def generate(
452461
openai_tools = self.tools.to_openai_toolbox()
453462

454463
try:
455-
stream = await self.client.chat.completions.create(
456-
messages=messages,
457-
model=self.model_name,
458-
tools=openai_tools if openai_tools else None,
459-
tool_choice="auto" if openai_tools else None,
460-
stream=True,
461-
stream_options={"include_usage": True}
462-
)
464+
create_params = {
465+
"messages": messages,
466+
"model": self.model_name,
467+
"tools": openai_tools if openai_tools else None,
468+
"tool_choice": "auto" if openai_tools else None,
469+
"stream": True,
470+
}
471+
472+
# Only OpenAI (and possibly others) support stream_options for usage
473+
if self.config.type == "openai":
474+
create_params["stream_options"] = {"include_usage": True}
475+
476+
stream = await self.client.chat.completions.create(**create_params)
463477

464478
tool_call_accumulator = {}
465479

0 commit comments

Comments
 (0)