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
6 changes: 6 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ services:
ports:
- "${APP_BIND:-127.0.0.1}:${APP_PORT:-7000}:7000"
volumes:
# Live host checkout for repository maintenance. The running app remains
# the image snapshot at /app.
- .:/project/workspace
# Disposable Linux-volume checkout for sandbox-first patch experiments.
- odysseus-workbench:/project/workbench
- ${APP_DATA_DIR:-./data}:/app/data:z
- ${APP_LOGS_DIR:-./logs}:/app/logs:z
# Cookbook remote-server SSH identity. Odysseus can generate a key here;
Expand Down Expand Up @@ -149,6 +154,7 @@ services:
restart: unless-stopped

volumes:
odysseus-workbench:
searxng-data:
chromadb-data:
ntfy-cache:
2 changes: 1 addition & 1 deletion docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ repair_bind_mount_ownership() {
# Repair image-owned writable paths without walking into bind-mounted host
# trees, then repair the app-owned mount roots separately.
repair_app_tree_ownership
for dir in /app/data /app/logs /app/.ssh /app/.cache/huggingface /app/.local; do
for dir in /app/data /app/logs /app/.ssh /app/.cache/huggingface /app/.local /project/workbench; do
repair_bind_mount_ownership "$dir"
done

Expand Down
21 changes: 17 additions & 4 deletions routes/chat_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
from core.models import ChatMessage
from src.request_models import ChatRequest
from src.llm_core import llm_call_async, stream_llm, stream_llm_with_fallback
from src.agent_loop import stream_agent_loop
from src.agent_loop import (
stream_agent_loop,
_agent_response_to_save,
_normalize_continuation_checkpoint,
)
from src import agent_runs
from src.model_context import estimate_tokens
from src.chat_helpers import coerce_message_and_session
Expand Down Expand Up @@ -584,6 +588,15 @@ async def chat_stream(request: Request) -> StreamingResponse:
approved_plan = ""
if not plan_mode:
approved_plan = (form_data.get("approved_plan") or "").strip()[:8192]
continuation_checkpoint = None
raw_checkpoint = form_data.get("continuation_checkpoint")
if raw_checkpoint:
try:
continuation_checkpoint = _normalize_continuation_checkpoint(
json.loads(str(raw_checkpoint)[:8192])
)
except (TypeError, ValueError, json.JSONDecodeError):
continuation_checkpoint = None
# Did the USER explicitly pick agent mode? (vs. us auto-escalating
# below). Skill extraction should only learn from real agent sessions,
# not chats we quietly promoted for a notes/calendar intent.
Expand Down Expand Up @@ -1429,6 +1442,7 @@ def _on_research_done(_sid, _result, _sources, _findings):
workspace=workspace or None,
forced_tools=_forced_tools,
uploaded_files=ctx.uploaded_files,
continuation_checkpoint=continuation_checkpoint,
):
if chunk.startswith("data: ") and not chunk.startswith("data: [DONE]"):
try:
Expand Down Expand Up @@ -1491,9 +1505,8 @@ def _on_research_done(_sid, _result, _sources, _findings):
elif chunk.startswith("event: "):
yield chunk
elif chunk == "data: [DONE]\n\n":
_has_tool_events = bool((last_metrics or {}).get("tool_events"))
if full_response or _has_tool_events:
_response_to_save = full_response or "Done."
_response_to_save = _agent_response_to_save(full_response, last_metrics)
if _response_to_save:
_metrics_to_save = dict(last_metrics or {})
if thinking_response.strip() and not _metrics_to_save.get("thinking"):
_metrics_to_save["thinking"] = thinking_response.strip()
Expand Down
4 changes: 3 additions & 1 deletion routes/chatgpt_subscription_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ def _provision_endpoint(tokens: Dict, owner: Optional[str]) -> Dict:
ep.api_key = None
ep.provider_auth_id = auth.id
ep.is_enabled = True
ep.supports_tools = False
# The provider adapter supports Responses function tools, streamed
# function calls, and structural function_call_output replay.
ep.supports_tools = True
ep.model_type = "llm"
ep.endpoint_kind = "api"
ep.model_refresh_mode = "manual"
Expand Down
72 changes: 72 additions & 0 deletions scripts/ODYSSEUS_DOCKER_START_STOP_README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
ODYSSEUS DOCKER START/STOP (WINDOWS)

REQUIREMENT
Docker Desktop must be installed, running, and ready before these scripts are used.
The scripts expect the repository at C:\odysseus and bind Odysseus to
http://127.0.0.1:7000.

START WITH A VISIBLE WINDOW
Double-click:
scripts\start_odysseus_docker.bat

START HIDDEN
Double-click:
scripts\start_odysseus_docker_hidden.vbs

The hidden launcher starts the same batch script without showing a console.
Startup details are appended to:
logs\odysseus-docker-startup.log

STOP
Double-click:
scripts\stop_odysseus_docker.bat

This runs "docker compose stop" for the standard stack. It does not run
"docker compose down", delete containers, volumes, images, or application data.
Stop details are appended to:
logs\odysseus-docker-stop.log

STATUS AND LOGS (POWERSHELL)
Set-Location C:\odysseus
docker compose ps
docker compose logs --tail 100 odysseus
docker compose logs --tail 100 searxng chromadb ntfy

PERSISTENCE
Application data and logs remain under C:\odysseus\data and C:\odysseus\logs.
SearXNG, ChromaDB, ntfy, and the disposable workbench use Docker named
volumes. The stop script preserves all of them.

REBUILD AFTER SOURCE CHANGES
Changes made in C:\odysseus (mounted as /project/workspace) do not change the
running application snapshot at /app. After reviewing and testing changes, rebuild and
recreate Odysseus from Windows PowerShell:
Set-Location C:\odysseus
$env:APP_BIND = "127.0.0.1"
$env:APP_PORT = "7000"
docker compose up -d --build --no-deps odysseus
Remove-Item Env:APP_BIND -ErrorAction SilentlyContinue
Remove-Item Env:APP_PORT -ErrorAction SilentlyContinue

WORKSPACE AND DISPOSABLE WORKBENCH
Select /project as the single active Odysseus workspace. The real Windows
checkout is mounted read/write at /project/workspace, while the running app
stays at /app. A Docker named volume is mounted at /project/workbench for
disposable sandbox-first patch work. Do not copy .env, data, logs, credentials,
model caches, or other secrets into the workbench. Test candidate changes
there, then apply the reviewed patch to /project/workspace and test again before
rebuilding.

ROLLBACK TO THE NATIVE WINDOWS LAUNCHER
The original native scripts are preserved:
scripts\start_odysseus.bat
scripts\start_odysseus_hidden.vbs
scripts\stop_odysseus.bat

Before starting native Odysseus, stop the Docker stack and confirm port 7000 is
free. Do not run the native and Docker versions on the same host port.

SAFETY
The standard Compose configuration does not mount the Docker socket. Do not add
the host-Docker overlay merely to use these scripts. Windows Firewall changes
are outside these scripts.
16 changes: 16 additions & 0 deletions scripts/ODYSSEUS_START_STOP_README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ODYSSEUS WINDOWS START/STOP

Copy these files to:
C:\odysseus\scripts

START:
- Double-click start_odysseus.bat for a visible console.
- Double-click start_odysseus_hidden.vbs for hidden startup.

STOP:
- Double-click stop_odysseus.bat.
- It stops only the process listening on port 7000.

AUTOSTART:
Create a shortcut to start_odysseus_hidden.vbs inside:
shell:startup
94 changes: 94 additions & 0 deletions scripts/rebuild_restart_odysseus_docker.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
@echo off
setlocal
title Rebuild and Restart Docker Odysseus

set "REPO=%~dp0.."
set "LOG=%REPO%\logs\odysseus-docker-rebuild.log"

cd /d "%REPO%" || (
echo ERROR: Could not enter the Odysseus repository.
exit /b 1
)

if not exist "%REPO%\logs" mkdir "%REPO%\logs"

echo.
echo ============================================================
echo Rebuild and restart Docker Odysseus
echo Repository: %REPO%
echo Log: %LOG%
echo ============================================================
echo.

where docker >nul 2>&1 || (
echo ERROR: Docker was not found. Start or install Docker Desktop.
exit /b 1
)

docker info >nul 2>&1 || (
echo ERROR: Docker Engine is unavailable. Start Docker Desktop and wait until it is ready.
exit /b 1
)

echo [%date% %time%] Validating Docker Compose configuration...>>"%LOG%"
docker compose config --quiet >>"%LOG%" 2>&1
if errorlevel 1 (
echo ERROR: Docker Compose validation failed.
echo Nothing was rebuilt or restarted.
echo Review: "%LOG%"
exit /b 1
)

set "APP_BIND=127.0.0.1"
set "APP_PORT=7000"

echo Building the Odysseus image with visible progress...
echo The first build can take several minutes. Do not close this window while progress continues.
echo Supporting services and persistent data will not be removed.
echo [%date% %time%] Starting image build...>>"%LOG%"

powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$enc = New-Object System.Text.UTF8Encoding($false); & cmd.exe /d /s /c 'docker compose --progress plain build odysseus 2>&1' | ForEach-Object { $line = $_.ToString(); Write-Host $line; [System.IO.File]::AppendAllText('%LOG%', $line + [Environment]::NewLine, $enc) }; exit $LASTEXITCODE"
if errorlevel 1 (
echo ERROR: The Odysseus image build failed.
echo The existing running container was not recreated.
echo Review: "%LOG%"
exit /b 1
)

echo.
echo Build succeeded. Recreating only the Odysseus container...
echo [%date% %time%] Recreating Odysseus service...>>"%LOG%"

powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$enc = New-Object System.Text.UTF8Encoding($false); & cmd.exe /d /s /c 'docker compose up -d --no-deps --force-recreate odysseus 2>&1' | ForEach-Object { $line = $_.ToString(); Write-Host $line; [System.IO.File]::AppendAllText('%LOG%', $line + [Environment]::NewLine, $enc) }; exit $LASTEXITCODE"
if errorlevel 1 (
echo ERROR: The image built successfully, but Odysseus recreation failed.
echo Review: "%LOG%"
exit /b 1
)

echo Waiting for Odysseus to become reachable...
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$ready=$false; for($i=1; $i -le 30; $i++){ try { $r=Invoke-WebRequest 'http://127.0.0.1:7000' -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop; if($r.StatusCode -ge 200 -and $r.StatusCode -lt 500){ Write-Host ('Odysseus is ready. HTTP status: ' + $r.StatusCode); $ready=$true; break } } catch {}; Write-Host ('Waiting... attempt ' + $i + ' of 30'); Start-Sleep -Seconds 2 }; if(-not $ready){ exit 1 }"
if errorlevel 1 (
echo.
echo ERROR: The container was recreated, but Odysseus did not become reachable within the allowed time.
echo Recent logs:
docker compose logs --tail 100 odysseus
echo.
echo Full rebuild log: "%LOG%"
exit /b 1
)

echo.
docker compose ps odysseus
if errorlevel 1 (
echo WARNING: Odysseus responded, but Docker Compose status could not be displayed.
)
echo.
echo SUCCESS: Docker Odysseus was rebuilt and restarted.
echo Open: http://127.0.0.1:7000
echo Log: "%LOG%"
echo [%date% %time%] Rebuild completed successfully.>>"%LOG%"
echo.

endlocal
exit /b 0
42 changes: 42 additions & 0 deletions scripts/start_odysseus.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@echo off
setlocal

set "ODYSSEUS_DIR=C:\odysseus"
set "HOST=127.0.0.1"
set "PORT=7000"
set "LOG_DIR=%ODYSSEUS_DIR%\logs"
set "LOG_FILE=%LOG_DIR%\odysseus-startup.log"

if not exist "%ODYSSEUS_DIR%\app.py" (
echo [%date% %time%] ERROR: Could not find "%ODYSSEUS_DIR%\app.py".
echo Check that Odysseus is installed in C:\odysseus.
pause
exit /b 1
)

if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"

cd /d "%ODYSSEUS_DIR%"

where python >nul 2>&1
if errorlevel 1 (
echo [%date% %time%] ERROR: Python was not found in PATH.>>"%LOG_FILE%"
echo Python was not found in PATH.
pause
exit /b 1
)

echo [%date% %time%] Starting Odysseus on http://%HOST%:%PORT%>>"%LOG_FILE%"

python -m uvicorn app:app --host %HOST% --port %PORT% >>"%LOG_FILE%" 2>&1

set "EXIT_CODE=%ERRORLEVEL%"
echo [%date% %time%] Odysseus exited with code %EXIT_CODE%.>>"%LOG_FILE%"

if not "%EXIT_CODE%"=="0" (
echo Odysseus stopped with exit code %EXIT_CODE%.
echo See "%LOG_FILE%" for details.
pause
)

exit /b %EXIT_CODE%
46 changes: 46 additions & 0 deletions scripts/start_odysseus_docker.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@echo off
setlocal EnableExtensions

set "ODYSSEUS_DIR=C:\odysseus"
set "LOG_DIR=%ODYSSEUS_DIR%\logs"
set "LOG_FILE=%LOG_DIR%\odysseus-docker-startup.log"
set "APP_BIND=127.0.0.1"
set "APP_PORT=7000"

if not exist "%ODYSSEUS_DIR%\docker-compose.yml" (
echo ERROR: Could not find "%ODYSSEUS_DIR%\docker-compose.yml".
exit /b 1
)

if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" >nul 2>&1
cd /d "%ODYSSEUS_DIR%"

where docker >nul 2>&1
if errorlevel 1 (
echo [%date% %time%] ERROR: Docker was not found in PATH.>>"%LOG_FILE%"
echo ERROR: Docker was not found in PATH. Install or start Docker Desktop.
exit /b 1
)

docker info >nul 2>&1
if errorlevel 1 (
echo [%date% %time%] ERROR: Docker Engine is unavailable.>>"%LOG_FILE%"
echo ERROR: Docker Engine is unavailable. Start Docker Desktop and wait until it is ready.
exit /b 1
)

echo [%date% %time%] Starting the standard Odysseus Docker stack on http://%APP_BIND%:%APP_PORT%>>"%LOG_FILE%"
docker compose up -d >>"%LOG_FILE%" 2>&1
set "EXIT_CODE=%ERRORLEVEL%"

if not "%EXIT_CODE%"=="0" (
echo [%date% %time%] ERROR: docker compose up failed with exit code %EXIT_CODE%.>>"%LOG_FILE%"
echo ERROR: Odysseus Docker startup failed with exit code %EXIT_CODE%.
echo See "%LOG_FILE%" for details.
exit /b %EXIT_CODE%
)

echo [%date% %time%] Docker Compose accepted the startup request.>>"%LOG_FILE%"
echo Odysseus Docker startup requested successfully.
echo Open http://%APP_BIND%:%APP_PORT% after the containers finish starting.
exit /b 0
16 changes: 16 additions & 0 deletions scripts/start_odysseus_docker_hidden.vbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Option Explicit

Dim shell, fso, scriptDir, batchFile
Set shell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")

scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
batchFile = fso.BuildPath(scriptDir, "start_odysseus_docker.bat")

If Not fso.FileExists(batchFile) Then
MsgBox "Could not find: " & batchFile, vbCritical, "Odysseus Docker startup"
WScript.Quit 1
End If

' 0 = hidden window; False = return immediately while the batch script runs.
shell.Run """" & batchFile & """", 0, False
16 changes: 16 additions & 0 deletions scripts/start_odysseus_hidden.vbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Option Explicit

Dim shell, fso, scriptDir, batchFile
Set shell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")

scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
batchFile = fso.BuildPath(scriptDir, "start_odysseus.bat")

If Not fso.FileExists(batchFile) Then
MsgBox "Could not find: " & batchFile, vbCritical, "Odysseus startup"
WScript.Quit 1
End If

' 0 = hidden window, False = do not wait for Uvicorn to exit.
shell.Run """" & batchFile & """", 0, False
Loading