git:20260527.14c2ed0 to v1.0.0

1 added, 0 removed. Audit B to B.

---
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
+ version: 1.0.0
---
# 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 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).
## Read State
```bash
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:**
```bash
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"
```
---
## Ensure Token is Valid
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.
---
## If ARTHUR_ENGINE_URL + ARTHUR_API_KEY + ARTHUR_TASK_ID All Exist
All downstream state is already set. Confirm reuse with user ("Use existing task `<ARTHUR_TASK_ID>`?") and exit this skill.
---
## Gate: Determine Application Type
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.
---
## Select or Create a Project
An Agentic Model belongs to a Project within a Workspace. List existing projects:
```bash
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:**
```bash
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:
```bash
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"
```
---
## List Existing Models in the Project
```bash
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`.
---
## Fetch Engine Connector
Before creating the Agentic Model, retrieve the `engine_internal` connector for the selected data plane. This `connector_id` is required in the creation payload.
```bash
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.
---
## Create Agentic Model
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.
```bash
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:
```bash
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"
```
---
## Poll Job Until Completed
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:
```bash
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`:
```bash
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:
```bash
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"
```
---
## Poll for Task Connection Info
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:
```bash
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:
- Guide the user to the platform UI: **Applications → <model name> → Task** tab to check for provisioning errors
- Ask the user if they can see a task ID in the UI and enter it manually (as a fallback)
Extract the connection info fields:
```bash
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).
---
## Verify Engine API Key
Confirm the connection info works against the GenAI Engine:
```bash
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 good
- `401` → API key is wrong; check connection info
- anything else → engine may not be reachable at `ENGINE_URL`; verify the engine is running and the URL is correct
---
## Save All Downstream State
```bash
STATE_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.