integrations/claude-code-skills/arthur-onboard/arthur-onboard-platform-model/SKILL.md
--- name: arthur-onboard-platform-model description: Arthur onboarding sub-skill — Platform Step 5: Gate on application type, create an Agentic Model on Arthur Platform, and retrieve GenAI Engine task connection info. Reads/writes .arthur-engine.env. allowed-tools: Bash, Read, Write --- # Arthur Onboard — Platform Step 5: Create Agentic Model **Goal:** Gate on application type; for Agentic applications, create a Project and Agentic Model on the platform, then retrieve GenAI Engine task connect
npx skillsauth add arthur-ai/arthur-engine integrations/claude-code-skills/arthur-onboard/arthur-onboard-platform-modelInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Goal: Gate on application type; for Agentic applications, create a Project and Agentic Model on the platform, then retrieve GenAI Engine task connection info to establish ARTHUR_ENGINE_URL, ARTHUR_API_KEY, and ARTHUR_TASK_ID in .arthur-engine.env — the same variables consumed by all downstream reused sub-skills (instrument, prompts, verify, eval-provider, evals).
cat .arthur-engine.env 2>/dev/null || echo "(no state file)"
Parse ARTHUR_PLATFORM_URL, ARTHUR_PLATFORM_TOKEN, ARTHUR_PLATFORM_WORKSPACE_ID, ARTHUR_PLATFORM_ENGINE_ID, ARTHUR_PLATFORM_PROJECT_ID, ARTHUR_PLATFORM_MODEL_ID, ARTHUR_ENGINE_URL, ARTHUR_API_KEY, ARTHUR_TASK_ID from the output.
State write helper:
STATE_FILE=".arthur-engine.env"
grep -v '^ARTHUR_TASK_ID=' "$STATE_FILE" 2>/dev/null > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp "$STATE_FILE" || true
echo "ARTHUR_TASK_ID=$TASK_ID" >> "$STATE_FILE"
Invoke the arthur-onboard-platform-token sub-skill to get a fresh token.
If it outputs TOKEN_REFRESH=MISSING_CREDENTIALS or TOKEN_REFRESH=FAILED: re-invoke arthur-onboard-platform-access to re-authenticate, then resume this skill.
All downstream state is already set. Confirm reuse with user ("Use existing task <ARTHUR_TASK_ID>?") and exit this skill.
Ask the user:
"What type of application are you onboarding?
A) Agentic application — LLM-powered agents, chatbots, RAG systems, or any app that emits traces. Creates an Agentic Model on the platform. B) ML Model — classification, regression, or tabular data models C) GenAI Model — LLM completions or generative models (non-agentic)"
Note: "Agentic Model" and "GenAI Model" are distinct model types on the Arthur Platform. This skill creates Agentic Models only. GenAI Models are a separate platform type and must be onboarded through the platform UI.
For B or C:
"For ML and GenAI Models, please log in to <ARTHUR_PLATFORM_URL>, navigate to Applications, and follow the platform UI wizard to onboard your model. The platform provides a guided onboarding flow for these model types."
"Exiting — this skill handles Agentic applications only."
Exit this skill without modifying state.
For A: Continue below.
An Agentic Model belongs to a Project within a Workspace. List existing projects:
PROJECTS_RESPONSE=$(curl -s \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
"${ARTHUR_PLATFORM_URL}/api/v1/workspaces/${ARTHUR_PLATFORM_WORKSPACE_ID}/projects")
PROJECT_LIST=$(echo "$PROJECTS_RESPONSE" | python3 -c "
import sys, json
d = json.load(sys.stdin)
projects = d.get('resources') or d.get('data') or d.get('projects') or (d if isinstance(d, list) else [])
for p in projects:
print(f' {p[\"id\"]}: {p[\"name\"]}')
if not projects:
print(' (no projects yet)')
" 2>/dev/null || echo " (error parsing response)")
echo "$PROJECT_LIST"
If ARTHUR_PLATFORM_PROJECT_ID exists in state, confirm reuse or allow re-selection.
Show the list and ask the user to select an existing project or create a new one.
To create a new project:
PROJECT_RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$PROJECT_NAME\"}" \
"${ARTHUR_PLATFORM_URL}/api/v1/workspaces/${ARTHUR_PLATFORM_WORKSPACE_ID}/projects")
PROJECT_ID=$(echo "$PROJECT_RESPONSE" | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
echo "PROJECT_ID=$PROJECT_ID"
Save ARTHUR_PLATFORM_PROJECT_ID to state:
STATE_FILE=".arthur-engine.env"
grep -v '^ARTHUR_PLATFORM_PROJECT_ID=' "$STATE_FILE" 2>/dev/null > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp "$STATE_FILE" || true
echo "ARTHUR_PLATFORM_PROJECT_ID=$PROJECT_ID" >> "$STATE_FILE"
MODELS_RESPONSE=$(curl -s \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
"${ARTHUR_PLATFORM_URL}/api/v1/projects/${PROJECT_ID}/models")
MODEL_LIST=$(echo "$MODELS_RESPONSE" | python3 -c "
import sys, json
d = json.load(sys.stdin)
models = d.get('records') or d.get('resources') or d.get('data') or d.get('models') or (d if isinstance(d, list) else [])
for m in models:
print(f' {m[\"id\"]}: {m[\"name\"]}')
if not models:
print(' (no models yet)')
" 2>/dev/null || echo " (error parsing response)")
echo "$MODEL_LIST"
Show the list and ask: "Select an existing model to instrument, or create a new one?"
If the user selects an existing model, skip to "Poll for Task Connection Info" with the selected MODEL_ID.
Before creating the Agentic Model, retrieve the engine_internal connector for the selected data plane. This connector_id is required in the creation payload.
CONNECTORS_RESPONSE=$(curl -s \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
"${ARTHUR_PLATFORM_URL}/api/v1/projects/${PROJECT_ID}/connectors?data_plane_id=${ARTHUR_PLATFORM_ENGINE_ID}&connector_type=engine_internal&page=1&page_size=1")
CONNECTOR_ID=$(echo "$CONNECTORS_RESPONSE" | python3 -c "
import sys, json
d = json.load(sys.stdin)
records = d.get('records') or d.get('resources') or d.get('data') or []
print(records[0]['id'] if records else '')
" 2>/dev/null)
echo "CONNECTOR_ID=$CONNECTOR_ID"
If CONNECTOR_ID is empty: show the raw response and stop. The engine must be registered and ARTHUR_PLATFORM_ENGINE_ID must be set in .arthur-engine.env. Re-run arthur-onboard-platform-engine if needed.
Ask the user for the model name (e.g., "Customer Support Bot", "Code Assistant").
Creating an Agentic Model submits an async task to the platform, which provisions the underlying GenAI Engine task. The call returns a job_id that must be polled to completion.
ONBOARDING_ID=$(python3 -c "import uuid; print(str(uuid.uuid4()))")
TASK_PAYLOAD=$(python3 -c "
import json
print(json.dumps({
'name': '$MODEL_NAME',
'onboarding_identifier': '$ONBOARDING_ID',
'connector_id': '$CONNECTOR_ID',
'is_agentic': True
}))
")
TASK_RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d "$TASK_PAYLOAD" \
"${ARTHUR_PLATFORM_URL}/api/v1/projects/${PROJECT_ID}/tasks")
JOB_ID=$(echo "$TASK_RESPONSE" | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('job_id',''))" 2>/dev/null)
echo "JOB_ID=$JOB_ID"
echo "ONBOARDING_ID=$ONBOARDING_ID"
If JOB_ID is empty: show the raw response; check if the endpoint or payload schema has changed by reviewing <ARTHUR_PLATFORM_URL>/api/docs.
Save ONBOARDING_ID and JOB_ID to state:
STATE_FILE=".arthur-engine.env"
grep -v '^ARTHUR_PLATFORM_ONBOARDING_ID=\|^ARTHUR_PLATFORM_JOB_ID=' "$STATE_FILE" 2>/dev/null > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp "$STATE_FILE" || true
echo "ARTHUR_PLATFORM_ONBOARDING_ID=$ONBOARDING_ID" >> "$STATE_FILE"
echo "ARTHUR_PLATFORM_JOB_ID=$JOB_ID" >> "$STATE_FILE"
The platform provisions the GenAI Engine task asynchronously. Poll the job status — make individual Bash calls one per check cycle. Because tokens expire in ~5 minutes, each call refreshes the token automatically:
ARTHUR_PLATFORM_URL=$(grep '^ARTHUR_PLATFORM_URL=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
JOB_ID=$(grep '^ARTHUR_PLATFORM_JOB_ID=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
# Refresh token using arthur_client (handles expiry automatically)
CLIENT_ID=$(grep '^ARTHUR_PLATFORM_CLIENT_ID=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
CLIENT_SECRET=$(grep '^ARTHUR_PLATFORM_CLIENT_SECRET=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
export _AE_URL="$ARTHUR_PLATFORM_URL" _AE_CLIENT_ID="$CLIENT_ID" _AE_CLIENT_SECRET="$CLIENT_SECRET"
ARTHUR_PLATFORM_TOKEN=$(python3 - <<'PYEOF'
import sys, os
try:
from arthur_client.auth import ArthurClientCredentialsAPISession, ArthurOIDCMetadata
metadata = ArthurOIDCMetadata(os.environ["_AE_URL"])
session = ArthurClientCredentialsAPISession(
client_id=os.environ["_AE_CLIENT_ID"],
client_secret=os.environ["_AE_CLIENT_SECRET"],
metadata=metadata,
)
print(session.token()["access_token"])
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
PYEOF
)
grep -v '^ARTHUR_PLATFORM_TOKEN=' .arthur-engine.env > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp .arthur-engine.env
echo "ARTHUR_PLATFORM_TOKEN=$ARTHUR_PLATFORM_TOKEN" >> .arthur-engine.env
JOB_RESPONSE=$(curl -s \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
"${ARTHUR_PLATFORM_URL}/api/v1/jobs/${JOB_ID}")
JOB_STATE=$(echo "$JOB_RESPONSE" | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))" 2>/dev/null || echo "unknown")
echo "JOB_STATE=$JOB_STATE"
Retry every 10 seconds, up to 30 times (~5 minutes). Stop when JOB_STATE=completed.
If JOB_STATE=failed: show the full job response and guide the user to check the platform UI under Applications → <model name> for provisioning errors. Exit this skill.
If still not completed after 30 attempts: guide the user to the platform UI to check job status.
Once the job is completed, retrieve the model using the onboarding_identifier:
ONBOARDING_ID=$(grep '^ARTHUR_PLATFORM_ONBOARDING_ID=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
PROJECT_ID=$(grep '^ARTHUR_PLATFORM_PROJECT_ID=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
MODELS_RESPONSE=$(curl -s \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
"${ARTHUR_PLATFORM_URL}/api/v1/projects/${PROJECT_ID}/models?onboarding_identifier=${ONBOARDING_ID}&page=1&page_size=1")
MODEL_ID=$(echo "$MODELS_RESPONSE" | python3 -c "
import sys, json
d = json.load(sys.stdin)
records = d.get('records') or d.get('resources') or d.get('data') or []
print(records[0]['id'] if records else '')
" 2>/dev/null)
echo "MODEL_ID=$MODEL_ID"
Save ARTHUR_PLATFORM_MODEL_ID to state:
STATE_FILE=".arthur-engine.env"
grep -v '^ARTHUR_PLATFORM_MODEL_ID=' "$STATE_FILE" 2>/dev/null > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp "$STATE_FILE" || true
echo "ARTHUR_PLATFORM_MODEL_ID=$MODEL_ID" >> "$STATE_FILE"
With the job complete and MODEL_ID known, poll for the task connection info — it is typically available immediately after the job completes. Make individual Bash calls one per check cycle and refresh the token each time:
ARTHUR_PLATFORM_URL=$(grep '^ARTHUR_PLATFORM_URL=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
ARTHUR_PLATFORM_TOKEN=$(grep '^ARTHUR_PLATFORM_TOKEN=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
MODEL_ID=$(grep '^ARTHUR_PLATFORM_MODEL_ID=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
# Refresh token using arthur_client (handles expiry automatically)
CLIENT_ID=$(grep '^ARTHUR_PLATFORM_CLIENT_ID=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
CLIENT_SECRET=$(grep '^ARTHUR_PLATFORM_CLIENT_SECRET=' .arthur-engine.env 2>/dev/null | cut -d= -f2-)
export _AE_URL="$ARTHUR_PLATFORM_URL" _AE_CLIENT_ID="$CLIENT_ID" _AE_CLIENT_SECRET="$CLIENT_SECRET"
ARTHUR_PLATFORM_TOKEN=$(python3 - <<'PYEOF'
import sys, os
try:
from arthur_client.auth import ArthurClientCredentialsAPISession, ArthurOIDCMetadata
metadata = ArthurOIDCMetadata(os.environ["_AE_URL"])
session = ArthurClientCredentialsAPISession(
client_id=os.environ["_AE_CLIENT_ID"],
client_secret=os.environ["_AE_CLIENT_SECRET"],
metadata=metadata,
)
print(session.token()["access_token"])
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
PYEOF
)
grep -v '^ARTHUR_PLATFORM_TOKEN=' .arthur-engine.env > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp .arthur-engine.env
echo "ARTHUR_PLATFORM_TOKEN=$ARTHUR_PLATFORM_TOKEN" >> .arthur-engine.env
CONN_RESPONSE=$(curl -s \
-H "Authorization: Bearer $ARTHUR_PLATFORM_TOKEN" \
"${ARTHUR_PLATFORM_URL}/api/v1/models/${MODEL_ID}/task/connection_info")
CONN_STATUS=$(echo "$CONN_RESPONSE" | \
python3 -c "
import sys, json
d = json.load(sys.stdin)
print('OK' if d.get('api_host') else 'WAITING')
" 2>/dev/null || echo "WAITING")
echo "CONN_STATUS=$CONN_STATUS"
Retry every 10 seconds, up to 10 times (~100 seconds). Stop when CONN_STATUS=OK.
If still WAITING after 10 attempts:
Extract the connection info fields:
TASK_ID=$(echo "$CONN_RESPONSE" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('task_id',''))" 2>/dev/null)
ENGINE_URL=$(echo "$CONN_RESPONSE" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('api_host',''))" 2>/dev/null)
ENGINE_API_KEY=$(echo "$CONN_RESPONSE" | \
python3 -c "
import sys, json
d = json.load(sys.stdin)
vk = d.get('validation_key') or {}
print(vk.get('key',''))
" 2>/dev/null)
echo "TASK_ID=$TASK_ID"
echo "ENGINE_URL=$ENGINE_URL"
echo "HAS_KEY=$([ -n "$ENGINE_API_KEY" ] && echo 'yes' || echo 'no')"
If any of the three values are empty after receiving CONN_STATUS=OK: show the full raw response and ask the user to identify the correct field names from the response JSON (the field names may differ across platform versions).
Confirm the connection info works against the GenAI Engine:
VERIFY_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $ENGINE_API_KEY" \
"${ENGINE_URL}/api/v2/tasks?page=0&page_size=1" 2>/dev/null || echo "000")
echo "ENGINE_API_VERIFY=$VERIFY_STATUS"
200 → all good401 → API key is wrong; check connection infoENGINE_URL; verify the engine is running and the URL is correctSTATE_FILE=".arthur-engine.env"
grep -v '^ARTHUR_ENGINE_URL=\|^ARTHUR_API_KEY=\|^ARTHUR_TASK_ID=' \
"$STATE_FILE" 2>/dev/null > /tmp/ae_env_tmp && mv /tmp/ae_env_tmp "$STATE_FILE" || true
echo "ARTHUR_ENGINE_URL=$ENGINE_URL" >> "$STATE_FILE"
echo "ARTHUR_API_KEY=$ENGINE_API_KEY" >> "$STATE_FILE"
echo "ARTHUR_TASK_ID=$TASK_ID" >> "$STATE_FILE"
Tell the user:
"Agentic model created and connected to the engine.
Engine URL: <ENGINE_URL> Task ID: <TASK_ID>
These have been saved to
.arthur-engine.env. The next steps will instrument your code to send traces to this engine."
Exit this skill.
tools
--- name: arthur-onboard-verify description: Arthur onboarding sub-skill — Step 7: Verify that traces are flowing from the instrumented application to Arthur Engine. Reads credentials from .arthur-engine.env. allowed-tools: Bash, Read --- # Arthur Onboard — Step 7: Verify Instrumentation ## Read State ```bash cat .arthur-engine.env 2>/dev/null || echo "(no state file)" ``` Parse `ARTHUR_ENGINE_URL`, `ARTHUR_API_KEY`, `ARTHUR_TASK_ID`. --- ## Tell the User to Run Their Application Show the
tools
--- name: arthur-onboard-task description: Arthur onboarding sub-skill — Step 3: Set up an Arthur Task (create or select). Reads/writes .arthur-engine.env. allowed-tools: Bash, Read, Write, Edit --- # Arthur Onboard — Step 3: Set Up Arthur Task **Goal:** Establish `ARTHUR_TASK_ID` in `.arthur-engine.env`. ## Read State ```bash cat .arthur-engine.env 2>/dev/null || echo "(no state file)" ``` Parse `ARTHUR_ENGINE_URL`, `ARTHUR_API_KEY`, and `ARTHUR_TASK_ID` from the output. **State write hel
tools
--- name: arthur-onboard-prompts description: Arthur onboarding sub-skill — Step 6: Extract prompts from the target repository and register them with Arthur Engine. Reads credentials from .arthur-engine.env. allowed-tools: Bash, Read, Task --- # Arthur Onboard — Step 6: Extract & Register Prompts ## Read State ```bash cat .arthur-engine.env 2>/dev/null || echo "(no state file)" ``` Parse `ARTHUR_ENGINE_URL`, `ARTHUR_API_KEY`, `ARTHUR_TASK_ID`. --- ## Extract Prompts via Sub-agent Delegate
development
Onboard an agentic application to the Arthur SaaS Platform (platform.arthur.ai). Guides through authentication, workspace selection, engine deployment, model creation, code instrumentation, trace verification, and eval configuration.