#!/usr/bin/env bash # ── Agent Workspace Manager ──────────────────────────────────────────── # Creates and maintains fully isolated environments per agent. # ~/Timmy-Time-dashboard is SACRED — never touched by agents. # # Each agent gets: # - Its own git clone (from Gitea, not the local repo) # - Its own port range (no collisions) # - Its own data/ directory (databases, files) # - Its own TIMMY_HOME (approvals.db, etc.) # - Shared Ollama backend (single GPU, shared inference) # - Shared Gitea (single source of truth for issues/PRs) # # Layout: # /tmp/timmy-agents/ # hermes/ — Hermes loop orchestrator # repo/ — git clone # home/ — TIMMY_HOME (approvals.db, etc.) # env.sh — source this for agent's env vars # kimi-0/ — Kimi pane 0 # repo/ # home/ # env.sh # ... # smoke/ — dedicated for smoke-testing main # repo/ # home/ # env.sh # # Usage: # agent_workspace.sh init — create or refresh # agent_workspace.sh reset — hard reset to origin/main # agent_workspace.sh branch
— fresh branch from main # agent_workspace.sh path — print repo path # agent_workspace.sh env — print env.sh path # agent_workspace.sh init-all — init all workspaces # agent_workspace.sh destroy — remove workspace entirely # ─────────────────────────────────────────────────────────────────────── set -o pipefail CANONICAL="$HOME/Timmy-Time-dashboard" AGENTS_DIR="/tmp/timmy-agents" GITEA_REMOTE="http://localhost:3000/rockachopa/Timmy-time-dashboard.git" TOKEN_FILE="$HOME/.hermes/gitea_token" # ── Port allocation (each agent gets a unique range) ────────────────── # Dashboard ports: 8100, 8101, 8102, ... (avoids real dashboard on 8000) # Serve ports: 8200, 8201, 8202, ... agent_index() { case "$1" in hermes) echo 0 ;; kimi-0) echo 1 ;; kimi-1) echo 2 ;; kimi-2) echo 3 ;; kimi-3) echo 4 ;; smoke) echo 9 ;; *) echo 0 ;; esac } get_dashboard_port() { echo $(( 8100 + $(agent_index "$1") )); } get_serve_port() { echo $(( 8200 + $(agent_index "$1") )); } log() { echo "[workspace] $*"; } # ── Get authenticated remote URL ────────────────────────────────────── get_remote_url() { if [ -f "$TOKEN_FILE" ]; then local token="" token=$(cat "$TOKEN_FILE" 2>/dev/null || true) if [ -n "$token" ]; then echo "http://hermes:${token}@localhost:3000/rockachopa/Timmy-time-dashboard.git" return fi fi echo "$GITEA_REMOTE" } # ── Create env.sh for an agent ──────────────────────────────────────── write_env() { local agent="$1" local ws="$AGENTS_DIR/$agent" local repo="$ws/repo" local home="$ws/home" local dash_port=$(get_dashboard_port "$agent") local serve_port=$(get_serve_port "$agent") cat > "$ws/env.sh" << EOF # Auto-generated agent environment — source this before running Timmy # Agent: $agent export TIMMY_WORKSPACE="$repo" export TIMMY_HOME="$home" export TIMMY_AGENT_NAME="$agent" # Ports (isolated per agent) export PORT=$dash_port export TIMMY_SERVE_PORT=$serve_port # Ollama (shared — single GPU) export OLLAMA_URL="http://localhost:11434" # Gitea (shared — single source of truth) export GITEA_URL="http://localhost:3000" # Test mode defaults export TIMMY_TEST_MODE=1 export TIMMY_DISABLE_CSRF=1 export TIMMY_SKIP_EMBEDDINGS=1 # Override data paths to stay inside the clone export TIMMY_DATA_DIR="$repo/data" export TIMMY_BRAIN_DB="$repo/data/brain.db" # Working directory cd "$repo" EOF chmod +x "$ws/env.sh" } # ── Init ────────────────────────────────────────────────────────────── init_workspace() { local agent="$1" local ws="$AGENTS_DIR/$agent" local repo="$ws/repo" local home="$ws/home" local remote remote=$(get_remote_url) mkdir -p "$ws" "$home" if [ -d "$repo/.git" ]; then log "$agent: refreshing existing clone..." cd "$repo" git remote set-url origin "$remote" 2>/dev/null git fetch origin --prune --quiet 2>/dev/null git checkout main --quiet 2>/dev/null git reset --hard origin/main --quiet 2>/dev/null git clean -fdx -e data/ --quiet 2>/dev/null else log "$agent: cloning from Gitea..." git clone "$remote" "$repo" --quiet 2>/dev/null cd "$repo" git fetch origin --prune --quiet 2>/dev/null fi # Ensure data directory exists mkdir -p "$repo/data" # Write env file write_env "$agent" log "$agent: ready at $repo (port $(get_dashboard_port "$agent"))" } # ── Reset ───────────────────────────────────────────────────────────── reset_workspace() { local agent="$1" local repo="$AGENTS_DIR/$agent/repo" if [ ! -d "$repo/.git" ]; then init_workspace "$agent" return fi cd "$repo" git merge --abort 2>/dev/null || true git rebase --abort 2>/dev/null || true git cherry-pick --abort 2>/dev/null || true git fetch origin --prune --quiet 2>/dev/null git checkout main --quiet 2>/dev/null git reset --hard origin/main --quiet 2>/dev/null git clean -fdx -e data/ --quiet 2>/dev/null log "$agent: reset to origin/main" } # ── Branch ──────────────────────────────────────────────────────────── branch_workspace() { local agent="$1" local branch="$2" local repo="$AGENTS_DIR/$agent/repo" if [ ! -d "$repo/.git" ]; then init_workspace "$agent" fi cd "$repo" git fetch origin --prune --quiet 2>/dev/null git branch -D "$branch" 2>/dev/null || true git checkout -b "$branch" origin/main --quiet 2>/dev/null log "$agent: on branch $branch (from origin/main)" } # ── Path ────────────────────────────────────────────────────────────── print_path() { echo "$AGENTS_DIR/$1/repo" } print_env() { echo "$AGENTS_DIR/$1/env.sh" } # ── Init all ────────────────────────────────────────────────────────── init_all() { for agent in hermes kimi-0 kimi-1 kimi-2 kimi-3 smoke; do init_workspace "$agent" done log "All workspaces initialized." echo "" echo " Agent Port Path" echo " ────── ──── ────" for agent in hermes kimi-0 kimi-1 kimi-2 kimi-3 smoke; do printf " %-9s %d %s\n" "$agent" "$(get_dashboard_port "$agent")" "$AGENTS_DIR/$agent/repo" done } # ── Destroy ─────────────────────────────────────────────────────────── destroy_workspace() { local agent="$1" local ws="$AGENTS_DIR/$agent" if [ -d "$ws" ]; then rm -rf "$ws" log "$agent: destroyed" else log "$agent: nothing to destroy" fi } # ── CLI dispatch ────────────────────────────────────────────────────── case "${1:-help}" in init) init_workspace "${2:?Usage: $0 init }" ;; reset) reset_workspace "${2:?Usage: $0 reset }" ;; branch) branch_workspace "${2:?Usage: $0 branch }" \ "${3:?Usage: $0 branch }" ;; path) print_path "${2:?Usage: $0 path }" ;; env) print_env "${2:?Usage: $0 env }" ;; init-all) init_all ;; destroy) destroy_workspace "${2:?Usage: $0 destroy }" ;; *) echo "Usage: $0 {init|reset|branch|path|env|init-all|destroy} [agent] [branch]" echo "" echo "Agents: hermes, kimi-0, kimi-1, kimi-2, kimi-3, smoke" exit 1 ;; esac