Skip to content

Deploy Guide: arc/agent-decouple → KVM Instance

Branch

arc/agent-decouple on git@github-claytantor:claytantor/tubman-bobtail-py.git

What this branch contains

  • KAT-63: tokens_selected moved from Agent to Portfolio (the correct owner)
  • Katpack foundation: role column (primary/observer) added to portfolio_agent_assignment
  • New GET /portfolio/{id}/agents endpoint (all assigned agents with roles)
  • Cross-user agent assignment support
  • run_agent accepts explicit agent_id for targeted recommendations
  • UI: agent cards, detail page, and portfolio settings updated for role-aware multi-agent assignment

Pre-flight checks

# Confirm you are on the correct host
hostname   # expected: tubman-claw-kvm

# Confirm Docker is running
docker ps

# Confirm you can reach the DB
docker exec katbot_postgres_db pg_isready -U postgres

Step 1 — Pull the branch

cd /path/to/tubman-bobtail-py   # adjust to your checkout location

git fetch origin
git checkout arc/agent-decouple
git pull origin arc/agent-decouple

Step 2 — Build the API image

Use docker/build.sh, not docker build directly — the build script stages the perpetuals-py dependency correctly.

bash docker/build.sh

To force a clean rebuild:

bash docker/build.sh --no-cache

Step 3 — Run the database migration

The migration e1a2b3c4d5e6_katpack_tokens_to_portfolio must run before starting the new image.

Option A — run inside the running container (if API is already up)

docker exec katbot_api bash -c "APP_CONFIG=/env/api.env alembic upgrade head"

Option B — run via a one-shot container (if API is stopped or not yet started)

docker run --rm \
  --network katbot \
  -v ${DEVOPS_BASEDIR}/env/${ENV}:/env \
  -v ${DEVOPS_BASEDIR}/migrations:/app/migrations \
  -e APP_CONFIG=/env/api.env \
  tubman-bobtail-py:latest \
  bash -c "alembic upgrade head"

Verify migration applied

docker exec katbot_api bash -c "APP_CONFIG=/env/api.env alembic current"
# Expected output includes: e1a2b3c4d5e6 (head)

Step 4 — Restart the API service

cd docker
docker compose up -d api

Wait for the health check to pass:

docker compose ps api
# STATUS column should show "healthy"

Step 5 — Smoke tests

API=http://localhost:8000
TOKEN="<your_jwt_token>"

# 1. tokens_selected on portfolio, not agent
curl -s -H "Authorization: Bearer $TOKEN" $API/portfolio | \
  python3 -c "import sys,json; d=json.load(sys.stdin); [print(p['name'], p.get('tokens_selected'), 'agent:', p.get('active_agent',{}).get('name') if p.get('active_agent') else None) for p in d]"

# 2. agent has no tokens_selected field
curl -s -H "Authorization: Bearer $TOKEN" $API/agents | \
  python3 -c "import sys,json; d=json.load(sys.stdin); [print(a['name'], 'has tokens?', 'tokens_selected' in a) for a in d]"
# Expected: "has tokens? False" for every agent

# 3. plural agents endpoint
PORTFOLIO_ID=<your_portfolio_id>
curl -s -H "Authorization: Bearer $TOKEN" $API/portfolio/$PORTFOLIO_ID/agents | python3 -m json.tool

# 4. add an observer assignment (non-destructive)
AGENT_ID=<another_agent_id>
curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"agent_id\": $AGENT_ID, \"role\": \"observer\"}" \
  $API/portfolio/$PORTFOLIO_ID/agent | python3 -m json.tool
# role should be "observer"; existing primary should be unchanged

# 5. recommendation with explicit agent_id
curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"message\": \"What is my current portfolio status?\", \"portfolio_id\": $PORTFOLIO_ID, \"agent_id\": $AGENT_ID}" \
  $API/agent/recommendation/message | python3 -m json.tool

Rollback

If anything goes wrong, downgrade the migration first, then roll back the image.

# 1. Downgrade migration
docker exec katbot_api bash -c "APP_CONFIG=/env/api.env alembic downgrade -1"

# 2. Tag the old image before you build new ones, or use a previous image tag
docker tag tubman-bobtail-py:previous tubman-bobtail-py:latest

# 3. Restart
cd docker && docker compose up -d api

Migration details (for reference)

Step What happens
Backfill portfolio.tokens_selected populated from active agent where NULL
Drop column agent.tokens_selected removed from the agent table
Add column role VARCHAR(32) DEFAULT 'primary' added to portfolio_agent_assignment
Backfill role All existing is_active=1 rows set to role='primary'

Downgrade reverses all four steps cleanly.