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
13 changes: 12 additions & 1 deletion templates/dkai-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Fork of **DKAI Arch** (`dkai-arch`) with the same Arch Linux base, AppArmor sand
- **`CURSOR_WORKER_DIR`** — git repo root for the worker (default `/home/coder/agent-workspace` when the template clones `cursor_worker_git_url`).
- **`CURSOR_WORKER_LABELS_FILE`** — optional JSON/TOML labels for the worker.
- **`CURSOR_WORKER_MANAGEMENT_ADDR`** — e.g. `:8080` for `/metrics`, `/healthz`, `/readyz` on the worker.
- **Slashbay pull** (orthogonal to Cloud Agent): workspace parameters **`slashbay_url`** and **`slashbay_worker_token`** inject **`SLASHBAY_URL`** / **`SLASHBAY_WORKER_TOKEN`** via `coder_env` (same pattern as `cursor_api_key`). When both are set, startup writes **`/home/coder/bin/slashbay-pull`** and `nohup`s it. The loop claims a job, clones `git_url` into **`/home/coder/agent-workspace/git/<repo>/`**, runs **`agent -p`**, and reports progress. Logs: **`/tmp/slashbay-pull.log`**. Pid: **`/tmp/slashbay-pull.pid`**. Do **not** put **`CODER_TOKEN`** in the workspace. The worker token is a pool secret, not a Cursor account. Keep **`start-cursor-worker`**.
- **Example labels file**: `~/.cursor-worker-labels.json.example` (copy and customize).

Workers only need **outbound HTTPS**; no inbound ports are required.
Expand Down Expand Up @@ -67,9 +68,18 @@ The **`coder`** binary stores global config under **`~/.config/coderv2`** (or **

The idle timeout is **`cursor_worker_idle_timeout`** (seconds).

### Slashbay warm pullers (2–5)

For [Slashbay](https://github.com/DataKnifeAI/slashbay) issue jobs, create a few **already running** workspaces from this template (do not create one workspace per issue):

1. Set **`slashbay_url`** to the Slashbay API (`https://slashbay.dataknife.net` or `http://slashbay.slashbay.svc.cluster.local`).
2. Set **`slashbay_worker_token`** to the same `SLASHBAY_WORKER_TOKEN` pool secret on every warm workspace.
3. Set **`cursor_api_key`** so `agent -p` can run (one named human seat).
4. Startup starts **`slashbay-pull`** in the background. Check **`/tmp/slashbay-pull.log`**.

## Relationship to DKAI Arch

Same Kubernetes layout (Deployment + **two** PVCs on `truenas-csi-nfs`, pod anti-affinity) and startup behavior (pacman, `gh`/`glab`, `kubectl`, `rancher`, `terraform`, Cursor sandbox `.deb` extract, `coder` CLI). Unlike **DKAI Arch**, this template does **not** include the `coder/cursor` registry module (no **Open in Cursor Desktop** app in Coder). Adds worker parameters, `coder_env` + end-of-startup autostart when **`cursor_api_key`** is set, `start-cursor-worker`, and related docs.
Same Kubernetes layout (Deployment + **two** PVCs on `truenas-csi-nfs`, pod anti-affinity) and startup behavior (pacman, `gh`/`glab`, `kubectl`, `rancher`, `terraform`, Cursor sandbox `.deb` extract, `coder` CLI). Unlike **DKAI Arch**, this template does **not** include the `coder/cursor` registry module (no **Open in Cursor Desktop** app in Coder). Adds worker parameters, `coder_env` + end-of-startup autostart when **`cursor_api_key`** is set, `start-cursor-worker`, optional **`slashbay-pull`** when Slashbay params are set, and related docs.

## Prerequisites

Expand All @@ -94,6 +104,7 @@ coder login <your-coder-url> # if needed
coder show <workspace-name> # e.g. copper-penguin-94 — check agent is connected / healthy
coder ssh <workspace-name> -- sh -c 'pgrep -af "agent worker|./coder agent"; test -n "$CURSOR_API_KEY" && echo CURSOR_API_KEY=set || echo CURSOR_API_KEY=missing'
coder ssh <workspace-name> -- sh -c 'tail -100 /tmp/cursor-worker.log 2>&1'
coder ssh <workspace-name> -- sh -c 'tail -100 /tmp/slashbay-pull.log 2>&1'
```

If **`CURSOR_API_KEY`** is missing in that SSH session, set the **`cursor_api_key`** workspace parameter or `export` it and run **`start-cursor-worker`** manually from `/home/coder/agent-workspace` (or your **`CURSOR_WORKER_DIR`**).
Expand Down
198 changes: 198 additions & 0 deletions templates/dkai-agent/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,31 @@ data "coder_parameter" "cursor_api_key" {
})
}

data "coder_parameter" "slashbay_url" {
name = "slashbay_url"
display_name = "Slashbay URL"
description = "Optional. Slashbay API base URL for the pull queue (e.g. https://slashbay.dataknife.net). When set with slashbay_worker_token, Coder injects SLASHBAY_URL via coder_env and auto-starts /home/coder/bin/slashbay-pull. Not a Coder token."
type = "string"
form_type = "input"
default = ""
mutable = true
icon = "/icon/github.svg"
}

data "coder_parameter" "slashbay_worker_token" {
name = "slashbay_worker_token"
display_name = "Slashbay worker token"
description = "Optional. Pool secret (SLASHBAY_WORKER_TOKEN) shared by warm dkai-agent pullers. Injected via coder_env when set. Not a Cursor account and not CODER_TOKEN."
type = "string"
form_type = "input"
default = ""
mutable = true
icon = "/emojis/1f511.png"
styling = jsonencode({
mask_input = true
})
}

provider "kubernetes" {
# Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
Expand Down Expand Up @@ -480,6 +505,145 @@ WORKERHELPER
# NFS / root_squash: chown may fail; 0755 still lets the coder user run the script as "other"
_chown_coder coder:coder /home/coder/bin/start-cursor-worker 2>/dev/null || true
ln -sf /home/coder/bin/start-cursor-worker /usr/local/bin/start-cursor-worker 2>/dev/null || true
cat > /home/coder/bin/slashbay-pull <<'PULLHELPER'
#!/usr/bin/env bash
set -euo pipefail
# Slashbay puller: claim a queued job and run agent -p. Do not start agent worker.
# Do not use VAR:-default brace expansion here: Terraform parses dollar-brace in startup_script.
SB_URL=$(printenv SLASHBAY_URL 2>/dev/null || true)
SB_TOKEN=$(printenv SLASHBAY_WORKER_TOKEN 2>/dev/null || true)
WS_NAME=$(printenv CODER_WORKSPACE_NAME 2>/dev/null || true)
if [ -z "$WS_NAME" ]; then
WS_NAME=$(hostname)
fi
if [ -z "$SB_URL" ] || [ -z "$SB_TOKEN" ]; then
echo "SLASHBAY_URL and SLASHBAY_WORKER_TOKEN required" >&2
exit 1
fi
SB_URL=$(printf '%s' "$SB_URL" | sed 's|/*$||')
IDLE_SECS=15
GIT_ROOT=/home/coder/agent-workspace/git
export HOME=/home/coder USER=coder LOGNAME=coder

job_field() {
python3 -c 'import json,sys
d=json.load(open("/tmp/slashbay-claim.json"))
k=sys.argv[1]
v=d.get(k,"")
if isinstance(v,list):
print(" ".join(str(x) for x in v))
else:
print(v)' "$1"
}

post_json() {
path=$1
body=$2
curl -sS -o /tmp/slashbay-api.out -w '%%{http_code}' \
-X POST \
-H "Authorization: Bearer $SB_TOKEN" \
-H "Content-Type: application/json" \
-d "$body" \
"$SB_URL$path" || true
}

progress() {
job_id=$1
status=$2
body=$(python3 -c 'import json,sys
print(json.dumps({"status": sys.argv[1], "workspace": sys.argv[2]}))' "$status" "$WS_NAME")
post_json "/v1/jobs/$job_id/progress" "$body" >/dev/null
}

complete() {
job_id=$1
ok=$2
text=$3
body=$(python3 -c 'import json,sys
ok=sys.argv[1]=="1"
payload={"ok": ok, "workspace": sys.argv[3]}
if ok:
payload["summary"]=sys.argv[2]
else:
payload["error"]=sys.argv[2]
print(json.dumps(payload))' "$ok" "$text" "$WS_NAME")
post_json "/v1/jobs/$job_id/complete" "$body" >/dev/null
}

echo "slashbay-pull: workspace=$WS_NAME url=$SB_URL"
while true; do
set +e
code=$(curl -sS -o /tmp/slashbay-claim.json -w '%%{http_code}' \
-H "Authorization: Bearer $SB_TOKEN" \
-H "X-Slashbay-Workspace: $WS_NAME" \
"$SB_URL/v1/jobs/claim?workspace=$WS_NAME")
curl_rc=$?
set -e
if [ "$curl_rc" -ne 0 ] || [ "$code" = "204" ] || [ -z "$code" ]; then
sleep "$IDLE_SECS"
continue
fi
if [ "$code" != "200" ]; then
echo "slashbay-pull: claim HTTP $code"
sleep "$IDLE_SECS"
continue
fi
job_id=$(job_field id)
prompt=$(job_field prompt)
git_url=$(job_field git_url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad claim JSON kills puller

Medium Severity

After a HTTP 200 claim, set -e is re-enabled and job_field parses /tmp/slashbay-claim.json with no error guard. Invalid JSON or a non-object body makes Python fail and exits the whole while true daemon until the workspace is restarted.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b5dd70. Configure here.

if [ -z "$job_id" ]; then
echo "slashbay-pull: claim body missing id"
sleep "$IDLE_SECS"
continue
fi
echo "slashbay-pull: claimed $job_id"
progress "$job_id" cloning
repo=$(printf '%s' "$git_url" | sed 's|/*$||; s|.*/||; s|\.git$||')
dest="$GIT_ROOT/$repo"
mkdir -p "$GIT_ROOT"
set +e
if [ -n "$git_url" ] && [ -n "$repo" ]; then
if [ -d "$dest/.git" ]; then
git -C "$dest" fetch --all --prune && git -C "$dest" pull --ff-only
else
git clone "$git_url" "$dest"
fi
clone_rc=$?
else
mkdir -p "$dest"
clone_rc=0
fi
set -e
if [ "$clone_rc" -ne 0 ]; then
complete "$job_id" 0 "git clone failed"
sleep "$IDLE_SECS"
continue
fi
cd "$dest" || cd /home/coder
progress "$job_id" agent_running
(
while true; do
sleep 60
progress "$job_id" agent_running
done
) &
hb=$!
set +e
agent -p "$prompt"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent cannot apply code changes

High Severity

slashbay-pull invokes agent -p without --force (and without --trust). In Cursor headless/print mode, writes and other tools are denied unless forced, so issue jobs can exit 0, get marked complete, and leave the repo unchanged.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b5dd70. Configure here.

agent_rc=$?
set -e
kill "$hb" 2>/dev/null || true
wait "$hb" 2>/dev/null || true
if [ "$agent_rc" -eq 0 ]; then
complete "$job_id" 1 "agent -p finished"
else
complete "$job_id" 0 "agent -p exited $agent_rc"
fi
done
PULLHELPER
chmod 0755 /home/coder/bin/slashbay-pull 2>/dev/null || true
_chown_coder coder:coder /home/coder/bin/slashbay-pull 2>/dev/null || true
ln -sf /home/coder/bin/slashbay-pull /usr/local/bin/slashbay-pull 2>/dev/null || true
if [ ! -f /home/coder/.cursor-worker-labels.json.example ]; then
printf "%s\n" \
"{" \
Expand Down Expand Up @@ -568,6 +732,12 @@ WORKERHELPER
echo " Or run manually: start-cursor-worker"
echo " (idle-release-timeout defaults from workspace parameter cursor_worker_idle_timeout)"
echo ""
echo "=== Slashbay pull (issue queue; orthogonal to Cloud Agent worker) ==="
echo " Set slashbay_url + slashbay_worker_token to auto-start slashbay-pull"
echo " Logs: /tmp/slashbay-pull.log pid: /tmp/slashbay-pull.pid"
echo " Or run manually: slashbay-pull"
echo " Do not put CODER_TOKEN in this workspace"
echo ""
echo "=== Other CLI (gh/glab auth is shared: /home/coder and /root use the same PVC paths) ==="
echo " GitHub: gh auth login # ok as coder or root; Cursor agent uses HOME=/home/coder"
echo " GitLab: glab auth login"
Expand Down Expand Up @@ -608,6 +778,20 @@ WORKERHELPER
else
echo "cursor_worker: helper missing at /home/coder/bin/start-cursor-worker"
fi
if [ -n "$${SLASHBAY_URL:-}" ] && [ -n "$${SLASHBAY_WORKER_TOKEN:-}" ] && [ -x /home/coder/bin/slashbay-pull ]; then
if [ -f /tmp/slashbay-pull.pid ]; then
read -r oldpid < /tmp/slashbay-pull.pid 2>/dev/null || oldpid=
[ -n "$${oldpid}" ] && kill "$${oldpid}" 2>/dev/null || true
rm -f /tmp/slashbay-pull.pid
fi
export HOME=/home/coder USER=coder LOGNAME=coder
nohup /home/coder/bin/slashbay-pull >>/tmp/slashbay-pull.log 2>&1 & echo $$! >/tmp/slashbay-pull.pid

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restart orphans agent processes

High Severity

Startup only kills the slashbay-pull bash PID from the pidfile, with no process-group cleanup, no wait, and no sleep before relaunch. Mid-job agent -p and the heartbeat subshell keep running, so restarts leave orphans and can double-claim work.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b5dd70. Configure here.

echo "slashbay_pull: started in background (log: /tmp/slashbay-pull.log, pid: /tmp/slashbay-pull.pid)"
elif [ -z "$${SLASHBAY_URL:-}" ] || [ -z "$${SLASHBAY_WORKER_TOKEN:-}" ]; then
echo "slashbay_pull: set slashbay_url and slashbay_worker_token to auto-start puller"
else
echo "slashbay_pull: helper missing at /home/coder/bin/slashbay-pull"
fi
EOT

# The following metadata blocks are optional. They are used to display
Expand Down Expand Up @@ -676,6 +860,20 @@ resource "coder_env" "cursor_api_key" {
value = data.coder_parameter.cursor_api_key.value
}

resource "coder_env" "slashbay_url" {
count = trimspace(data.coder_parameter.slashbay_url.value) != "" ? 1 : 0
agent_id = coder_agent.main.id
name = "SLASHBAY_URL"
value = data.coder_parameter.slashbay_url.value
}

resource "coder_env" "slashbay_worker_token" {
count = trimspace(data.coder_parameter.slashbay_worker_token.value) != "" ? 1 : 0
agent_id = coder_agent.main.id
name = "SLASHBAY_WORKER_TOKEN"
value = data.coder_parameter.slashbay_worker_token.value
}

resource "kubernetes_persistent_volume_claim_v1" "home" {
metadata {
name = "coder-${data.coder_workspace.me.id}-home"
Expand Down