forked from Rockachopa/Timmy-time-dashboard
## Workspace Isolation No agent touches ~/Timmy-Time-dashboard anymore. Each agent gets a fully isolated clone under /tmp/timmy-agents/ with its own port, data directory, and TIMMY_HOME. - scripts/agent_workspace.sh: init, reset, branch, destroy per agent - Loop prompt updated: workspace paths replace worktree paths - Smoke tests run in isolated /tmp/timmy-agents/smoke/repo ## Honest Success Metrics Cycle success now requires BOTH hermes clean exit AND main green (smoke test passes). Tracks main_green_rate separately from hermes_clean_rate in summary.json. Follows from PR #162 (triage + retro system). Co-authored-by: Kimi Agent <kimi@timmy.local> Reviewed-on: http://localhost:3000/rockachopa/Timmy-time-dashboard/pulls/186 Co-authored-by: hermes <hermes@timmy.local> Co-committed-by: hermes <hermes@timmy.local>
246 lines
8.7 KiB
Bash
246 lines
8.7 KiB
Bash
#!/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 <agent> — create or refresh
|
|
# agent_workspace.sh reset <agent> — hard reset to origin/main
|
|
# agent_workspace.sh branch <agent> <br> — fresh branch from main
|
|
# agent_workspace.sh path <agent> — print repo path
|
|
# agent_workspace.sh env <agent> — print env.sh path
|
|
# agent_workspace.sh init-all — init all workspaces
|
|
# agent_workspace.sh destroy <agent> — 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 <agent>}" ;;
|
|
reset) reset_workspace "${2:?Usage: $0 reset <agent>}" ;;
|
|
branch) branch_workspace "${2:?Usage: $0 branch <agent> <branch>}" \
|
|
"${3:?Usage: $0 branch <agent> <branch>}" ;;
|
|
path) print_path "${2:?Usage: $0 path <agent>}" ;;
|
|
env) print_env "${2:?Usage: $0 env <agent>}" ;;
|
|
init-all) init_all ;;
|
|
destroy) destroy_workspace "${2:?Usage: $0 destroy <agent>}" ;;
|
|
*)
|
|
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
|