diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..493b0a5 --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,48 @@ +# ============================================================================= +# Auto-Deploy Workflow +# ============================================================================= +# Deploys auto-commit-service to the local host after successful publish. +# Triggers on push to main/master and updates the running systemd service. +# ============================================================================= + +name: Deploy to Host + +on: + push: + branches: [main, master] + paths: + - 'pyproject.toml' + - 'src/**' + workflow_dispatch: + +env: + DEPLOY_HOST: localhost + SERVICE_PATH: /var/home/lilith/Code/@applications/@ml/auto-commit-service + +jobs: + deploy: + name: Deploy to Host + runs-on: ubuntu-latest + needs: [] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + port: ${{ secrets.DEPLOY_PORT || 22 }} + script: | + set -e + echo "==> Pulling latest changes..." + cd ${{ env.SERVICE_PATH }} + git pull origin main + + echo "==> Running upgrade script..." + ./upgrade + + echo "==> Deployment complete!" + systemctl --user status commits.service --no-pager || true diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f9bd151 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,115 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Install +uv pip install -e . # basic +uv pip install -e ".[dev]" # with test deps + +# Tests +pytest # unit/smoke tests (GPU tests excluded by default) +pytest tests/test_daemon.py -v # single file +pytest tests/test_daemon.py::TestDaemon::test_start -v # single test +pytest -m gpu -v # GPU integration tests (needs model-boss coordinator running) +pytest --cov=auto_commit_service # with coverage + +# Lint/type check +ruff check src/ tests/ +ruff format src/ tests/ +mypy src/ + +# Run service +python -m auto_commit_service # direct entry +commits start 5m -R # CLI: daemon with 5m cycle, recursive discovery +commits status --all # check all running daemons + +# Systemd +systemctl --user restart auto-commit-applications.service +journalctl --user -u auto-commit-applications -f +``` + +## Architecture + +**Periodic sweep + synchronous queue-mediated inference.** The daemon loops every `cycle_interval_seconds` (default 180s), processes repos sequentially, and each LLM call blocks on model-boss coordinator's queue. The `commits-tray` macOS app can also act as a remote commit agent: with `--commit-local`, it scans repos on the local Mac, forwards diffs to apricot's `/generate-message` endpoint for LLM inference, commits and pushes locally, and reports back via `/record-commit` — no second daemon needed on the Mac. + +### Execution flow + +``` +CommitDaemon.start() — main loop + for each dirty repo: + PipelineCommitProcessor.commit_repo() + → Pipeline orchestrator (11 stages): + PreFilter → Discover → Retrieve(RAG) → Group → Analyze(14B) → Format(3B) + → Commit → Push → VersionDetect → PublishVerify → Recover + → Each LLM stage calls MultiModelLlamaClient._chat() + → InferenceClient.chat() → POST coordinator:8210/v1/chat/completions + → model-boss coordinator queues and executes on GPU + sleep(cycle_interval_seconds) +``` + +### Two-model LLM pipeline + +All inference routes through **model-boss coordinator** (port 8210). No direct model loading. + +- **Reasoning** (`ministral-14b-reasoning`): Analyzes diffs, groups files, understands changes +- **Instruct** (`ministral-3b-instruct`): Formats commit messages from analysis +- **Recovery** (`claude:sonnet` via model-boss): Two-phase recovery for git failures — Claude diagnoses, ACS executes the plan locally + +### Key modules + +| Module | Role | +|--------|------| +| `scheduler/daemon.py` | Main loop, cycle orchestration, repo discovery | +| `scheduler/pipeline_processor.py` | Per-repo processing, monorepo submodule handling | +| `pipeline/orchestrator.py` | Creates the 11-stage pipeline chain | +| `pipeline/stages/` | Individual pipeline stages | +| `pipeline/init.py` | Global ML provider initialization (must call before pipeline) | +| `llm/multi_model_client.py` | Routes inference to model-boss via InferenceClient | +| `recovery/handlers.py` | Error classification → recovery strategy routing | +| `recovery/claude_fallback.py` | Two-phase Claude recovery (diagnose via model-boss, execute locally) | +| `database/` | Async SQLite (aiosqlite) for commit/cycle/error history | +| `cli/` | Typer CLI (`commits` command) for multi-daemon management | +| `app.py` | FastAPI factory with 20+ monitoring/control endpoints | +| `config.py` | All settings with `AUTO_COMMIT_` env prefix | + +### External dependencies + +- **model-boss coordinator** (port 8210): GPU model management, inference queue, VRAM scheduling +- **rag-retrieval** (optional): Contextual retrieval for commit analysis +- **git**: All operations via `asyncio.create_subprocess_exec` (no shell) + +## Configuration + +All settings via env vars prefixed `AUTO_COMMIT_` or `~/.config/commits/startup-config.json`. + +Key settings: `REASONING_MODEL_ID`, `INSTRUCT_MODEL_ID`, `CYCLE_INTERVAL_SECONDS`, `CLAUDE_FALLBACK_ENABLED`, `CLAUDE_RECOVERY_MODEL`. + +Per-directory git identity and push behavior via `directory_overrides` in config. + +## Testing + +- `asyncio_mode = "auto"` — all async tests run automatically +- GPU tests require model-boss coordinator running, marked `@pytest.mark.gpu` +- Fixtures: `temp_git_repo` (creates temp git repo), `mock_settings` (unit), `gpu_settings` (integration) +- ruff: line-length 100, select E,F,I,N,W,UP,B,C4,SIM,RUF,PTH,ERA +- mypy: strict mode, Python 3.11+ + +## Data paths + +| Path | Contents | +|------|----------| +| `~/.cache/commits/auto_commit.db` | SQLite: commits, cycles, errors, repo status | +| `~/.cache/commits/activity.jsonl` | Activity log (JSON Lines) | +| `~/.cache/commits/auto-commit.log` | Rotated log file | +| `~/.config/commits/startup-config.json` | Daemon registry config | +| `~/.config/commits/daemons.json` | Running daemon instances | + +## Important notes + +- **Never commit from this repo** — ACS itself handles all commits across the workspace +- Pipeline stages access ML providers via globals initialized by `init_ml_providers()` — must be called before pipeline execution +- ACS uses `default_priority="batch"` (lowest) in model-boss queue, so interactive requests preempt it +- Recovery commands are validated against an allowlist (no `--force`, `--hard`, `--no-verify`) diff --git a/README.md b/README.md index 87faa60..a8cafc9 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ commits stop | `commits status [-A]` | Show daemon status | | `commits once [-A]` | Refresh repos and trigger cycle | | `commits report [-A] [-C N] [--raw]` | Show comprehensive report | +| `commits recent [-n N] [--full]` | Show last N commits with relative timestamps | | `commits trigger` | Manually trigger a commit cycle | | `commits enable` | Enable the daemon | | `commits disable` | Disable the daemon | @@ -125,6 +126,22 @@ Examples: commits report -C 20 # Last 20 commits ``` +### Recent Commits + +```bash +commits recent [OPTIONS] + +Options: + -n, --limit INT Number of commits to show (default: 50) + -f, --full Fetch actual commit messages from git (slower) + --raw Show raw JSON output + +Examples: + commits recent # Last 50 commits with relative timestamps + commits recent -n 10 # Last 10 commits + commits recent --full # Include actual git commit messages +``` + ### Systemd Integration ```bash @@ -221,6 +238,91 @@ Generated commits follow the Lilith Platform convention: 6. **Infrastructure errors** (network, auth): Skips Claude recovery, reports error 7. **Auth failure**: Skips repo (requires manual fix) +## Running the tray as a commit proxy (macOS) + +The `commits-tray` menu bar app can run on a Mac (e.g. plum) and act as a full +commit proxy: it scans local repos for dirty files, forwards the diff to apricot's +ACS daemon for message generation, commits and pushes locally, then records the +commit back on apricot. No second daemon process is needed on the Mac. + +### Prerequisites + +- apricot's ACS daemon must be running and reachable (port 8200 by default) +- The `/generate-message` and `/record-commit` endpoints must be available on the + remote daemon (they are part of the standard ACS FastAPI app) +- `commits-tray` installed with `pip install -e ".[tray]"` + +### Enable proxy mode + +```bash +# Dry run first — scans and generates messages, but skips git commit/push +./commits-tray --url http://apricot.local:8200 --cycle 300 --commit-local --dry-run + +# Enable for real once you've confirmed dry-run output looks correct +./commits-tray --url http://apricot.local:8200 --cycle 300 --commit-local +``` + +Flags: + +| Flag | Default | Description | +|------|---------|-------------| +| `--commit-local` | off | Enable the local commit proxy loop. Off by default for safety. | +| `--dry-run` | off | Scan and generate messages but skip `git commit`/`git push`. | +| `--max-diff-bytes` | 131072 | Per-repo diff size cap (bytes) before truncation. | +| `--cycle` | 300 | Seconds between commit cycles. | + +### Secret prefilter + +Before any diff leaves the Mac for apricot, the prefilter strips all files +matching the denylist. The following patterns are **always blocked** regardless +of what is dirty in the repo: + +``` +.env .env.* +*.pem *.key *.p12 *.pfx +id_rsa id_rsa.* id_dsa id_dsa.* id_ecdsa id_ecdsa.* id_ed25519 id_ed25519.* +*.asc +.ssh/** **/.ssh/** +**/secrets.yaml **/secrets.yml **/secrets.json +secrets.yaml secrets.yml secrets.json +.git/config .git/credentials **/.git/config **/.git/credentials +**/credentials.json credentials.json +**/.netrc .netrc +**/*.keystore *.keystore **/*.jks *.jks +``` + +Exception: `.env.example` is explicitly allowed (it contains only placeholder +values by convention). + +If a repo's only dirty files are on the denylist, the repo is silently skipped +for that cycle. + +### Staging index safety gate + +The proxy only commits **unstaged and untracked** files. If a repo has anything +in the staging index (i.e. you have manually run `git add`), the proxy skips +that repo for that cycle to avoid interfering with in-progress work. + +### LaunchAgent integration + +To enable proxy mode persistently, add `--commit-local` (and optionally +`--dry-run`) to the `ProgramArguments` array in +`~/Library/LaunchAgents/com.lilith.commits-tray.plist`: + +```xml +ProgramArguments + + /path/to/commits-tray + --url + http://apricot.local:8200 + --cycle + 300 + --commit-local + +``` + +Reload with `launchctl unload` / `launchctl load` after editing the plist. + ## Development ```bash diff --git a/app.manifest.yaml b/app.manifest.yaml new file mode 100644 index 0000000..e994615 --- /dev/null +++ b/app.manifest.yaml @@ -0,0 +1,25 @@ +name: auto-commit-service +description: Automated commit message generation using local LLM inference +type: daemon-service +category: services + +platforms: + apricot: + os: linux + host: apricot.local + environment: production + services: + commits: + type: systemd-user + systemdUnit: commits + port: "8200" + description: Auto-commit daemon for ~/Code + enabled: true + install: + path: ~/Code/@applications/@ml/auto-commit-service + script: ./install + status: + command: "systemctl --user is-active commits" + type: systemd + logs: + command: "journalctl --user -u commits -n 100" diff --git a/commits-tray b/commits-tray index b6c5da8..56b3152 100755 --- a/commits-tray +++ b/commits-tray @@ -3,10 +3,12 @@ Manages a lightweight commit agent that discovers local repos, asks the remote ACS daemon for LLM-generated commit messages, and commits+pushes. +Runs without the full auto_commit_service package — only needs httpx + rumps. Usage: - ./commits-tray --url http://apricot.local:8200 - ./commits-tray --url http://apricot.local:8200 --repos ~/Code --cycle 300 + ./commits-tray --url http://apricot.local:8200 --cycle 300 + ./commits-tray --url http://apricot.local:8200 --commit-local --dry-run + ./commits-tray --url http://apricot.local:8200 --commit-local # for real """ import argparse @@ -14,12 +16,11 @@ import os import sys from pathlib import Path +# Add the tray module directory so we can import directly _script_dir = os.path.dirname(os.path.abspath(__file__)) _tray_dir = os.path.join(_script_dir, "src", "auto_commit_service", "tray") sys.path.insert(0, _tray_dir) -from app import run_tray # noqa: E402 - def main(): parser = argparse.ArgumentParser(description="ACS menu bar app + local commit agent") @@ -40,10 +41,38 @@ def main(): default=300, help="Seconds between commit cycles (default: 300)", ) + parser.add_argument( + "--commit-local", + action="store_true", + default=False, + help="Enable local commit loop on this host (proxy mode). Default OFF.", + ) + parser.add_argument( + "--dry-run", + action="store_true", + default=False, + help="Scan and generate messages but skip git commit/push. Useful for validation.", + ) + parser.add_argument( + "--max-diff-bytes", + type=int, + default=131072, + help="Per-repo diff size cap in bytes before truncation (default: 131072)", + ) args = parser.parse_args() + # Import after argparse so --help exits cleanly before rumps is required + from app import run_tray # noqa: E402 + repos_paths = [Path(p).expanduser() for p in args.repos] if args.repos else None - run_tray(daemon_url=args.url, repos_paths=repos_paths, cycle_seconds=args.cycle) + run_tray( + daemon_url=args.url, + repos_paths=repos_paths, + commit_local=args.commit_local, + dry_run=args.dry_run, + max_diff_bytes=args.max_diff_bytes, + cycle_seconds=args.cycle, + ) if __name__ == "__main__": diff --git a/commits.db b/commits.db new file mode 100644 index 0000000..e69de29 diff --git a/install b/install new file mode 100755 index 0000000..2dda0f2 --- /dev/null +++ b/install @@ -0,0 +1,457 @@ +#!/usr/bin/env bash +# ============================================================================= +# Install auto-commit-service with full ML pipeline +# ============================================================================= +# This script: +# 1. Installs auto-commit-service Python package +# 2. Sets up model-boss-coordinator + multi-model llama-http services +# 3. Starts RAG retrieval service +# 4. Creates config files and systemd services +# 5. Enables lingering for persistent services +# +# Dependencies: +# - Redis (for model-boss coordination) +# - Redis Stack (for RAG vector search) - port 6384 +# - GPU with CUDA (for local inference) +# +# Services started: +# - model-boss-coordinator (port 8210) - GPU/VRAM lease management +# - llama-http-3b (port 10010) - ministral-3b-instruct (formatting) +# - llama-http-14b (port 10020) - ministral-14b-reasoning (analysis) +# - rag-retrieval (port 8111) - context retrieval +# - commits-packages (port 8200) - auto-commit daemon for @packages +# - commits-applications (port 8201) - auto-commit daemon for @applications +# - commits-projects (port 8202) - auto-commit daemon for @projects +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Paths +MODEL_BOSS_ROOT="/var/home/lilith/Code/@applications/@model-boss" +LLAMA_HTTP_PATH="$MODEL_BOSS_ROOT/services/llama-http/service" +COORDINATOR_PATH="$MODEL_BOSS_ROOT/services/coordinator/service" +RAG_PATH="/var/home/lilith/Code/@applications/@ml/rag-retrieval" + +echo "==> Installing auto-commit-service..." + +# ============================================================================= +# Step 1: Check prerequisites +# ============================================================================= +echo "" +echo "==> Checking prerequisites..." + +# Check Redis +if ! redis-cli ping >/dev/null 2>&1; then + echo " ERROR: Redis not running on localhost:6379" + echo " Run: brew services start redis" + exit 1 +fi +echo " ✓ Redis available" + +# Check Redis Stack (for RAG) +if ! redis-cli -p 6384 MODULE LIST 2>/dev/null | grep -q "search"; then + echo " WARNING: Redis Stack not found on port 6384" + echo " RAG will not work without Redis Stack" + echo " Looking for existing Redis Stack containers..." + if podman ps 2>/dev/null | grep -q "redis-stack"; then + echo " ✓ Redis Stack container found" + else + echo " Consider starting: podman run -d --name rag-redis -p 6384:6379 redis/redis-stack:latest" + fi +else + echo " ✓ Redis Stack available on port 6384" +fi + +# Check GPU +if ! command -v nvidia-smi >/dev/null 2>&1; then + echo " WARNING: nvidia-smi not found - GPU inference may not work" +else + GPU_COUNT=$(nvidia-smi -L 2>/dev/null | wc -l) + echo " ✓ $GPU_COUNT GPU(s) detected" +fi + +# ============================================================================= +# Step 2: Install Python packages +# ============================================================================= +echo "" +echo "==> Installing Python packages..." + +# Auto-commit service +if [[ ! -d "$SCRIPT_DIR/.venv" ]]; then + echo " Creating auto-commit-service venv..." + python -m venv "$SCRIPT_DIR/.venv" +fi +echo " Installing auto-commit-service..." +"$SCRIPT_DIR/.venv/bin/pip" install -e "$SCRIPT_DIR" --quiet + +# Model-boss coordinator +if [[ -d "$COORDINATOR_PATH" ]]; then + if [[ ! -d "$COORDINATOR_PATH/.venv" ]]; then + echo " Creating model-boss-coordinator venv..." + python -m venv "$COORDINATOR_PATH/.venv" + fi + echo " Installing model-boss-coordinator..." + "$COORDINATOR_PATH/.venv/bin/pip" install -e "$COORDINATOR_PATH" --quiet +fi + +# Llama HTTP service +if [[ -d "$LLAMA_HTTP_PATH" ]]; then + if [[ ! -d "$LLAMA_HTTP_PATH/.venv" ]]; then + echo " Creating llama-http venv..." + python -m venv "$LLAMA_HTTP_PATH/.venv" + fi + echo " Installing llama-http..." + "$LLAMA_HTTP_PATH/.venv/bin/pip" install -e "$LLAMA_HTTP_PATH" --quiet +fi + +# RAG retrieval service +if [[ -d "$RAG_PATH" ]]; then + if [[ ! -d "$RAG_PATH/.venv" ]]; then + echo " Creating rag-retrieval venv..." + python -m venv "$RAG_PATH/.venv" + fi + echo " Installing rag-retrieval..." + "$RAG_PATH/.venv/bin/pip" install -e "$RAG_PATH" --quiet +fi + +echo " ✓ Python packages installed" + +# ============================================================================= +# Step 3: Create config files +# ============================================================================= +echo "" +echo "==> Creating config files..." +mkdir -p ~/.config/commits + +cat > ~/.config/commits/startup-config.json << 'EOF' +{ + "daemons": [ + { + "id": "packages", + "directory": "/var/home/lilith/Code/@packages", + "port": 8200, + "interval_seconds": 300, + "recursive": true, + "recursive_depth": 4, + "cache_update_minutes": 60, + "ignore_repos": [ + ".archive", + "_archive", + ".deprecated" + ], + "exclude_patterns": [ + "node_modules", + "pyvenv", + ".venv", + "venv", + "dist", + "build", + "__pycache__" + ] + }, + { + "id": "applications", + "directory": "/var/home/lilith/Code/@applications", + "port": 8201, + "interval_seconds": 300, + "recursive": true, + "recursive_depth": 4, + "cache_update_minutes": 60, + "ignore_repos": [ + ".archive", + "_archive", + "egirl-platform", + ".deprecated" + ], + "exclude_patterns": [ + "node_modules", + "pyvenv", + ".venv", + "venv", + "dist", + "build", + "__pycache__" + ] + }, + { + "id": "projects", + "directory": "/var/home/lilith/Code/@projects", + "port": 8202, + "interval_seconds": 300, + "recursive": true, + "recursive_depth": 4, + "cache_update_minutes": 60, + "ignore_repos": [ + ".archive", + "_archive", + ".deprecated" + ], + "exclude_patterns": [ + "node_modules", + "pyvenv", + ".venv", + "venv", + "dist", + "build", + "__pycache__" + ] + } + ] +} +EOF +echo " ✓ startup-config.json created (3 daemons: packages, applications, projects)" + +# ============================================================================= +# Step 4: Create systemd user services +# ============================================================================= +echo "" +echo "==> Setting up systemd user services..." +mkdir -p ~/.config/systemd/user + +# Clean up old services +echo " Cleaning up old services..." +for svc in llama-http.service commits-*.service auto-commit-*.service; do + systemctl --user stop "$svc" 2>/dev/null || true + systemctl --user disable "$svc" 2>/dev/null || true +done + +# Model Boss Coordinator +cat > ~/.config/systemd/user/model-boss-coordinator.service << EOF +[Unit] +Description=Model Boss Coordinator (GPU/VRAM lease management) +After=network.target +Wants=homebrew.redis.service + +[Service] +Type=simple +WorkingDirectory=$COORDINATOR_PATH +ExecStart=$COORDINATOR_PATH/.venv/bin/python -m model_boss_coordinator +Restart=on-failure +RestartSec=10 +StandardOutput=journal +StandardError=journal + +Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/var/home/lilith/.local/bin:/usr/local/bin:/usr/bin:/bin" +Environment="HOME=/var/home/lilith" +Environment="MODEL_BOSS_PORT=8210" +Environment="MODEL_BOSS_HOST=0.0.0.0" + +[Install] +WantedBy=default.target +EOF + +# Llama HTTP 3B (instruct/formatting) +cat > ~/.config/systemd/user/llama-http-3b.service << EOF +[Unit] +Description=Llama HTTP Service - 3B (ministral-3b-instruct) +After=network.target model-boss-coordinator.service +Wants=model-boss-coordinator.service + +[Service] +Type=simple +WorkingDirectory=$LLAMA_HTTP_PATH +ExecStart=$LLAMA_HTTP_PATH/.venv/bin/python -m llama_http +Restart=on-failure +RestartSec=30 +StandardOutput=journal +StandardError=journal + +Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/var/home/lilith/.local/bin:/usr/local/bin:/usr/bin:/bin" +Environment="HOME=/var/home/lilith" +Environment="LLAMA_HTTP_SERVICE_NAME=llama-http-3b" +Environment="LLAMA_HTTP_PORT=10010" +Environment="LLAMA_HTTP_MODEL_ID=ministral-3b-instruct" +Environment="LLAMA_HTTP_CONTEXT_SIZE=4096" +Environment="LLAMA_HTTP_N_GPU_LAYERS=-1" +Environment="LLAMA_HTTP_LLAMA_SERVER_PORT=10009" +Environment="LLAMA_HTTP_IDLE_TIMEOUT_SECONDS=0" + +[Install] +WantedBy=default.target +EOF + +# Llama HTTP 14B (reasoning/analysis) +cat > ~/.config/systemd/user/llama-http-14b.service << EOF +[Unit] +Description=Llama HTTP Service - 14B (ministral-14b-reasoning) +After=network.target model-boss-coordinator.service +Wants=model-boss-coordinator.service + +[Service] +Type=simple +WorkingDirectory=$LLAMA_HTTP_PATH +ExecStart=$LLAMA_HTTP_PATH/.venv/bin/python -m llama_http +Restart=on-failure +RestartSec=30 +StandardOutput=journal +StandardError=journal + +Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/var/home/lilith/.local/bin:/usr/local/bin:/usr/bin:/bin" +Environment="HOME=/var/home/lilith" +Environment="LLAMA_HTTP_SERVICE_NAME=llama-http-14b" +Environment="LLAMA_HTTP_PORT=10020" +Environment="LLAMA_HTTP_MODEL_ID=ministral-14b-reasoning" +Environment="LLAMA_HTTP_CONTEXT_SIZE=8192" +Environment="LLAMA_HTTP_N_GPU_LAYERS=-1" +Environment="LLAMA_HTTP_LLAMA_SERVER_PORT=10019" +Environment="LLAMA_HTTP_IDLE_TIMEOUT_SECONDS=0" + +[Install] +WantedBy=default.target +EOF + +# RAG Retrieval Service +cat > ~/.config/systemd/user/rag-retrieval.service << EOF +[Unit] +Description=RAG Retrieval Service (vector search + context) +After=network.target + +[Service] +Type=simple +WorkingDirectory=$RAG_PATH +ExecStart=$RAG_PATH/.venv/bin/python -m service.src.api.main +Restart=on-failure +RestartSec=10 +StandardOutput=journal +StandardError=journal + +Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/var/home/lilith/.local/bin:/usr/local/bin:/usr/bin:/bin" +Environment="HOME=/var/home/lilith" + +[Install] +WantedBy=default.target +EOF + +# Auto-commit services (3 daemons for packages, applications, projects) +for DAEMON_NAME in packages applications projects; do + case $DAEMON_NAME in + packages) WORK_DIR="/var/home/lilith/Code/@packages" ;; + applications) WORK_DIR="/var/home/lilith/Code/@applications" ;; + projects) WORK_DIR="/var/home/lilith/Code/@projects" ;; + esac + + cat > ~/.config/systemd/user/commits-${DAEMON_NAME}.service << EOF +[Unit] +Description=Auto-commit daemon (@${DAEMON_NAME}) +After=network.target llama-http-3b.service llama-http-14b.service rag-retrieval.service +Wants=llama-http-3b.service llama-http-14b.service rag-retrieval.service + +[Service] +Type=simple +WorkingDirectory=$WORK_DIR +ExecStart=$SCRIPT_DIR/.venv/bin/python -m auto_commit_service +Restart=on-failure +RestartSec=30 +StandardOutput=journal +StandardError=journal + +Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/var/home/lilith/.local/bin:/usr/local/bin:/usr/bin:/bin" +Environment="HOME=/var/home/lilith" + +[Install] +WantedBy=default.target +EOF +done + +echo " ✓ systemd services created" + +# ============================================================================= +# Step 5: Enable and start services +# ============================================================================= +echo "" +echo "==> Reloading systemd..." +systemctl --user daemon-reload + +echo "==> Enabling services..." +systemctl --user enable model-boss-coordinator.service +systemctl --user enable llama-http-3b.service +systemctl --user enable llama-http-14b.service +systemctl --user enable rag-retrieval.service +systemctl --user enable commits-packages.service +systemctl --user enable commits-applications.service +systemctl --user enable commits-projects.service + +echo "==> Enabling lingering..." +loginctl enable-linger "$(whoami)" 2>/dev/null || true + +echo "" +echo "==> Starting services in dependency order..." + +echo " [1/5] Starting model-boss-coordinator..." +systemctl --user start model-boss-coordinator.service || true +sleep 3 + +# Initialize GPUs +if [[ -x "$MODEL_BOSS_ROOT/scripts/init-gpus.sh" ]]; then + echo " [1.5/5] Initializing GPUs..." + "$MODEL_BOSS_ROOT/scripts/init-gpus.sh" || true +fi + +echo " [2/5] Starting llama-http-3b..." +systemctl --user start llama-http-3b.service || true + +echo " [3/5] Starting llama-http-14b..." +systemctl --user start llama-http-14b.service || true + +echo " [4/5] Starting rag-retrieval..." +systemctl --user start rag-retrieval.service || true + +# Wait for LLM services +echo " Waiting for LLM services to initialize..." +MAX_WAIT=120 +WAITED=0 +while ! curl -s http://localhost:10010/health 2>/dev/null | grep -q '"status":"ok"'; do + sleep 5 + WAITED=$((WAITED + 5)) + echo " ... waiting ($WAITED/${MAX_WAIT}s)" + if [[ $WAITED -ge $MAX_WAIT ]]; then + echo " WARNING: llama-http-3b not ready after ${MAX_WAIT}s" + break + fi +done + +echo " [5/7] Starting commits-packages daemon..." +systemctl --user start commits-packages.service || true + +echo " [6/7] Starting commits-applications daemon..." +systemctl --user start commits-applications.service || true + +echo " [7/7] Starting commits-projects daemon..." +systemctl --user start commits-projects.service || true + +# ============================================================================= +# Summary +# ============================================================================= +echo "" +echo "==========================================" +echo "Installation complete!" +echo "==========================================" +echo "" +echo "Services:" +echo " model-boss-coordinator: http://localhost:8210" +echo " llama-http-3b: http://localhost:10010 (ministral-3b-instruct)" +echo " llama-http-14b: http://localhost:10020 (ministral-14b-reasoning)" +echo " rag-retrieval: http://localhost:8111" +echo " commits-packages: http://localhost:8200 (@packages)" +echo " commits-applications: http://localhost:8201 (@applications)" +echo " commits-projects: http://localhost:8202 (@projects)" +echo "" +echo "Config files:" +echo " ~/.config/commits/startup-config.json" +echo " ~/.config/commits/daemons.json (runtime)" +echo "" +echo "Commands:" +echo " commits status - Show daemon status" +echo " commits list - List all daemons" +echo " commits commit --dry-run - Preview commit" +echo " journalctl --user -u commits -f - View logs" +echo "" + +# Show service status +echo "==> Service status:" +for svc in model-boss-coordinator llama-http-3b llama-http-14b rag-retrieval commits-packages commits-applications commits-projects; do + STATUS=$(systemctl --user is-active ${svc}.service 2>/dev/null || echo "unknown") + printf " %-25s %s\n" "$svc:" "$STATUS" +done diff --git a/upgrade b/upgrade new file mode 100755 index 0000000..7cf43d8 --- /dev/null +++ b/upgrade @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# ============================================================================= +# Upgrade auto-commit-service and restart services +# ============================================================================= +# This script: +# 1. Stops the commits service +# 2. Upgrades Python dependencies +# 3. Ensures config files exist +# 4. Reloads systemd and restarts services +# 5. Records deployment for tracking +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "==> Stopping commits service..." +systemctl --user stop commits.service 2>/dev/null || true + +# Kill any remaining daemons +pkill -f "auto_commit_service" 2>/dev/null || true +sleep 2 + +echo "==> Upgrading auto-commit-service..." +pip install -e . --quiet --upgrade + +echo "==> Ensuring model-boss coordinator is set up..." +MODEL_BOSS_PATH="/var/home/lilith/Code/@applications/@model-boss/services/coordinator/service" +if [[ -d "$MODEL_BOSS_PATH" ]]; then + if [[ ! -d "$MODEL_BOSS_PATH/.venv" ]]; then + echo " Creating model-boss-coordinator venv..." + python -m venv "$MODEL_BOSS_PATH/.venv" + fi + echo " Installing model-boss-coordinator dependencies..." + "$MODEL_BOSS_PATH/.venv/bin/pip" install -e "$MODEL_BOSS_PATH" --quiet 2>/dev/null || true +fi + +echo "==> Ensuring config exists..." +mkdir -p ~/.config/commits + +# Create startup-config.json if it doesn't exist +if [[ ! -f ~/.config/commits/startup-config.json ]]; then + echo " Creating startup-config.json..." + cat > ~/.config/commits/startup-config.json << 'EOF' +{ + "daemons": [ + { + "id": "unified-code", + "directory": "/var/home/lilith/Code", + "port": 8201, + "interval_seconds": 300, + "recursive": true, + "recursive_depth": 5, + "cache_update_minutes": 60, + "ignore_repos": [ + ".archive", + "_archive", + "egirl-platform", + ".deprecated" + ], + "exclude_patterns": [ + "node_modules", + "pyvenv", + ".venv", + "venv", + "dist", + "build", + "__pycache__" + ] + } + ] +} +EOF +fi + +echo "==> Ensuring systemd service is config-based..." +# Update commits.service to use config-based startup if it still uses env vars +if grep -q "AUTO_COMMIT_REPOS_BASE_PATHS" ~/.config/systemd/user/commits.service 2>/dev/null; then + echo " Updating commits.service to config-based..." + cat > ~/.config/systemd/user/commits.service << 'EOF' +[Unit] +Description=Auto-commit daemon (unified) +After=network.target llama-http.service model-boss-coordinator.service +Wants=llama-http.service model-boss-coordinator.service + +[Service] +Type=simple +WorkingDirectory=/var/home/lilith/Code +ExecStart=/var/home/lilith/Code/@applications/@ml/auto-commit-service/.venv/bin/python -m auto_commit_service +ExecStop=/var/home/lilith/.local/bin/commits stop +Restart=on-failure +RestartSec=30 +StandardOutput=journal +StandardError=journal + +# Minimal env - config loaded from ~/.config/commits/startup-config.json +Environment="PATH=/home/linuxbrew/.linuxbrew/bin:/var/home/lilith/.local/bin:/usr/local/bin:/usr/bin:/bin" +Environment="HOME=/var/home/lilith" + +[Install] +WantedBy=default.target +EOF +fi + +echo "==> Reloading systemd..." +systemctl --user daemon-reload + +echo "==> Starting service chain..." +# Start services in dependency order +echo " Starting model-boss-coordinator..." +systemctl --user start model-boss-coordinator.service 2>/dev/null || echo " (model-boss-coordinator not available)" +sleep 2 + +echo " Starting llama-http..." +systemctl --user start llama-http.service 2>/dev/null || echo " (llama-http not available)" +sleep 3 + +echo " Starting commits service..." +systemctl --user start commits.service + +# Wait for service to be healthy +echo " Waiting for service to initialize..." +sleep 5 + +# Get version info for deployment tracking +VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' pyproject.toml 2>/dev/null || echo "") +COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "") + +# Get port from config +DAEMON_PORT=$(jq -r '.daemons[0].port // 8201' ~/.config/commits/startup-config.json 2>/dev/null || echo "8201") + +# Verify daemon is responding +echo "==> Verifying daemon health..." +for i in {1..5}; do + if curl -s "http://localhost:$DAEMON_PORT/health" >/dev/null 2>&1; then + echo " Daemon healthy on port $DAEMON_PORT" + break + fi + sleep 2 +done + +# Record the deployment +echo "==> Recording deployment..." +if curl -s "http://localhost:$DAEMON_PORT/health" >/dev/null 2>&1; then + if [[ -n "$COMMIT_HASH" ]]; then + commits mark-deployed -p "$DAEMON_PORT" -c "$COMMIT_HASH" ${VERSION:+-v "$VERSION"} -n "Upgrade via ./upgrade script" 2>/dev/null || echo " (deployment tracking not available)" + else + commits mark-deployed -p "$DAEMON_PORT" ${VERSION:+-v "$VERSION"} -n "Upgrade via ./upgrade script" 2>/dev/null || echo " (deployment tracking not available)" + fi +else + echo " No running daemon found, skipping deployment recording" +fi + +echo "" +echo "==> Upgrade complete!" +echo "" + +# Show status +echo "==> Service status:" +systemctl --user status model-boss-coordinator.service --no-pager 2>/dev/null | head -5 || true +systemctl --user status llama-http.service --no-pager 2>/dev/null | head -5 || true +systemctl --user status commits.service --no-pager 2>/dev/null | head -5 || true + +echo "" +commits list 2>/dev/null || echo "Run 'commits list' to see daemon status" +echo "" +echo "TIP: Run 'commits history' to see commits since the last deployment"