Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ OPENROUTER_BASE_URL=https://openrouter.ai/api/v1

# Ollama (local; no API key required — run: ollama serve && ollama pull <model>)
OLLAMA_BASE_URL=http://localhost:11434/v1
OLLAMA_MODEL=llama3.2
OLLAMA_MODEL=qwen3-coder-next:cloud

# ------------------------------------------------------------------------------
# Slack API Credentials (https://api.slack.com/apps)
Expand Down
Binary file modified agent/__pycache__/__init__.cpython-311.pyc
Binary file not shown.
Binary file modified agent/__pycache__/config.cpython-311.pyc
Binary file not shown.
4 changes: 2 additions & 2 deletions agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class Config:
OPENROUTER_MODEL: str = os.getenv("OPENROUTER_MODEL", "anthropic/claude-sonnet-4-5")
OPENROUTER_BASE_URL: str = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "llama3.2")
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "lfm2.5-thinking:latest")

# Slack
SLACK_BOT_TOKEN: str = os.getenv("SLACK_BOT_TOKEN", "")
Expand Down Expand Up @@ -123,7 +123,7 @@ class Config:
TERMINAL_TOOL_CONFIG: Path = _CONFIG_DIR / "terminal_allowed_commands.yaml"

# LLM request timeout
LLM_REQUEST_TIMEOUT_SECONDS: int = int(os.getenv("LLM_REQUEST_TIMEOUT_SECONDS", "90"))
LLM_REQUEST_TIMEOUT_SECONDS: int = int(os.getenv("LLM_REQUEST_TIMEOUT_SECONDS", "200"))

# Deep Research Agent
MAX_RESEARCH_ITERATIONS: int = int(os.getenv("MAX_RESEARCH_ITERATIONS", "20"))
Expand Down
19 changes: 15 additions & 4 deletions agent/nodes/deep_researcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
# Inject a SYSTEM NOTE into the next iteration when the same pattern is seen this many times.
_SAME_PATTERN_WARN_THRESHOLD = 2
# Force actions=[] (immediate hand-off) when the same pattern is seen this many times.
_SAME_PATTERN_FORCE_PIVOT = 3
# Lowered from 3 to 2 to catch loops earlier and avoid wasted iterations.
_SAME_PATTERN_FORCE_PIVOT = 2
# Inject a SYSTEM NOTE after this many consecutive iterations that added zero new files.
_ZERO_NEW_FILES_INJECT_THRESHOLD = 3
# Force exit (hand-off) after this many consecutive zero-gain iterations.
Expand Down Expand Up @@ -381,9 +382,16 @@ def _normalize_pattern(action: Dict[str, Any]) -> str:
Produce a normalized fingerprint for a search action so we can detect
when the planner is repeating the same query. Lowercases and strips
leading/trailing whitespace and quote characters.

Includes tool + path in the key to distinguish searches with same pattern
but different scopes (e.g., search_code vs find_definitions, or path="all" vs specific repo).
"""
raw = action.get("pattern") or action.get("symbol") or ""
return raw.lower().strip().strip("\"'")
tool = action.get("tool", "")
raw = action.get("pattern") or action.get("symbol", "")
path = action.get("path", "all")
# Normalize: tool|path|pattern for more accurate deduplication
normalized = f"{tool}|{path}|{raw}".lower().strip().strip("\"'")
return normalized


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -538,6 +546,7 @@ def __call__(self, state: ConversationState) -> ConversationState:
if key:
pattern_seen_counts[key] = pattern_seen_counts.get(key, 0) + 1
count = pattern_seen_counts[key]
print(f"[DeepResearcher] 🔍 Pattern tracker: {key[:70]!r} seen {count}x")
if count >= _SAME_PATTERN_FORCE_PIVOT:
print(f"[DeepResearcher] ⚠ Force-pivot: pattern seen {count}x — {key[:80]!r}")
log.info("[DeepResearcher] force-pivot pattern=%r count=%d", key[:80], count)
Expand Down Expand Up @@ -746,7 +755,9 @@ def _execute_search_action(action: Dict[str, Any], _iter: int = iteration) -> tu
history_entry = (
f"Iteration {iteration}\n"
f" Intent: {search_intent}\n"
f" Thinking: {(thinking or '').strip()[:220]}\n\n"
f" Thinking: {(thinking or '').strip()[:300]}\n"
f" Actions: {len(actions)} tool calls\n"
f" Results: {added_this_iter} new files found\n"
+ "\n\n".join(action_blocks)
)
search_history_entries.append(history_entry)
Expand Down
Binary file modified data/slack_agent.db
Binary file not shown.
35 changes: 25 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# docker compose -f docker-compose.yml -f docker-compose.build.yml up --build
#
# Ollama runs by default (service `ollama`). Set LLM_PROVIDER=ollama and pull a model, e.g.:
# docker compose exec ollama ollama pull llama3.2
# docker compose exec ollama ollama pull lfm2.5-thinking:latest
#
# Configuration lives in this file (no separate .env required).
#
Expand All @@ -25,8 +25,8 @@ x-postgres-password: &postgres-password "${POSTGRES_PASSWORD:-slack_agent_dev}"
# Shared application environment for the agent service.
x-app-environment: &app-environment
# --- Identity (optional; config/agent.yaml is the default source) ---
AGENT_NAME: ""
COMPANY_NAME: ""
AGENT_NAME: "Alex"
COMPANY_NAME: "OLake"

# --- LLM Provider to use---
LLM_PROVIDER: ollama
Expand All @@ -46,9 +46,9 @@ x-app-environment: &app-environment
OPENROUTER_MODEL: anthropic/claude-sonnet-4-5
OPENROUTER_BASE_URL: ${OPENROUTER_BASE_URL}

# --- Ollama ---
# --- Ollama (agent reaches the ollama service on the compose network) ---
OLLAMA_BASE_URL: http://ollama:11434/v1
OLLAMA_MODEL: ${OLLAMA_MODEL}
OLLAMA_MODEL: ${OLLAMA_MODEL:-lfm2.5-thinking:latest}

# --- Slack (required for the agent service) ---
SLACK_BOT_TOKEN: ${SLACK_BOT_TOKEN}
Expand All @@ -71,7 +71,7 @@ x-app-environment: &app-environment
HIGH_PRIORITY_CHANNELS: ""

# --- Tuning ---
LLM_REQUEST_TIMEOUT_SECONDS: "90"
LLM_REQUEST_TIMEOUT_SECONDS: "300"
MAX_RESEARCH_ITERATIONS: "20"
MAX_CONTEXT_FILES: "15"
RESEARCH_TIMEOUT_SECONDS: "120"
Expand Down Expand Up @@ -101,9 +101,9 @@ services:
test: ["CMD-SHELL", "pg_isready -U slack_agent -d slack_agent"]
interval: 5s
timeout: 5s
retries: 10
retries: 3

# Local LLM — runs by default; use with LLM_PROVIDER=ollama (pull models into the volume first).
# Local LLM — image default CMD is `ollama serve` (listens on :11434). Data in ollama_data volume.
ollama:
image: ollama/ollama:latest
restart: unless-stopped
Expand All @@ -115,9 +115,22 @@ services:
test: ["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"]
interval: 15s
timeout: 5s
retries: 5
retries: 3
start_period: 15s

# One-shot: pull OLLAMA_MODEL into the shared volume after the server is ready (first run may take a while).
ollama-pull:
image: ollama/ollama:latest
restart: "no"
volumes:
- ollama_data:/root/.ollama
environment:
OLLAMA_HOST: http://ollama:11434
entrypoint: ["ollama", "pull", "${OLLAMA_MODEL:-lfm2.5-thinking:latest}"]
depends_on:
ollama:
condition: service_healthy

agent:
image: siddharthchevella/slack-agent:latest
restart: unless-stopped
Expand All @@ -133,11 +146,13 @@ services:
condition: service_healthy
ollama:
condition: service_healthy
ollama-pull:
condition: service_completed_successfully
healthcheck:
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8001/health')\""]
interval: 15s
timeout: 5s
retries: 5
retries: 3
start_period: 20s

volumes:
Expand Down