skills/agent-canvas-environment/SKILL.md
Work effectively inside a local Agent Canvas environment, including local agent-server auth, frontend/backend port discovery, safe workspace hygiene, and delegating work to a new local conversation through POST /api/conversations.
npx skillsauth add openhands/extensions agent-canvas-environmentInstall 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.
Use this skill when running inside or alongside a local Agent Canvas stack, especially when the user asks to inspect the local backend, create or monitor local conversations, or delegate work to another local conversation.
http://localhost:8001.http://localhost:8000.X-Session-API-Key.git status -sb. If a worktree has unrelated changes, use a separate worktree or clone.Use the first available value, without echoing it:
KEY="${SESSION_API_KEY:-${OH_SESSION_API_KEYS_0:-${LOCAL_BACKEND_API_KEY:-}}}"
if [ -z "$KEY" ] && [ -f "$HOME/.openhands/agent-canvas/api-key.txt" ]; then
KEY="$(tr -d '\n' < "$HOME/.openhands/agent-canvas/api-key.txt")"
fi
test -n "$KEY" || { echo "No Agent Canvas session API key found" >&2; exit 1; }
Validate backend access:
curl -sS -o /tmp/agent-canvas-conversations.json -w '%{http_code}\n' \
-H "X-Session-API-Key: $KEY" \
http://localhost:8001/api/conversations/search
HTTP 200 means the backend and key work.
Use POST /api/conversations with:
agent_settings from GET /api/settings (with X-Expose-Secrets: encrypted), which carries the real Fernet-encrypted llm.api_key, the existing agent_context, and the agent kind — so you never handle plaintext credentials and you don't drop the caller's skill/context configsecrets_encrypted: true so the agent-server decrypts that api_key server-sideagent_settings.tools (and task_tool_set when you enable sub-agents)tool_module_qualnames for any non-SDK tools (e.g. canvas_ui)agent_context.load_public_skills/load_user_skills/load_project_skills set to true if the delegated agent should inherit bundled/user/project skillsinitial_message.run: trueworktree: false when the workspace is already isolatedGET /api/settings (default) masks every credential — llm.api_key comes back as the literal string "**********". If you forward that verbatim, the new conversation authenticates with the placeholder and fails immediately with LLMAuthenticationError (You must provide an API key).
The supported way to obtain forwardable credentials is the X-Expose-Secrets: encrypted request header. With it, /api/settings returns the real llm.api_key as a Fernet-encrypted token (starts with gAAAAA) intended to be sent back to the server with secrets_encrypted: true; the agent-server's decrypt_incoming_llm_secrets decrypts it server-side. Do not read ~/.openhands/profiles/*.json directly — that is brittle (the caller may not share the backend's home directory, active_profile may be null, the profile store may live elsewhere).
Two working approaches:
agent_profile_id (simplest, but no tools) — send only agent_profile_id: "<uuid>" (from GET /api/agent-profiles → the profile whose id equals active_agent_profile_id from /api/settings). The server resolves the LLM key + agent kind from the profile. Mutually exclusive with agent/agent_settings, and the openhands agent-profile schema forbids tools/include_default_tools, so the conversation gets zero exec tools this way. Use only when the task needs no tools.
Encrypted agent_settings (full tools, preserves context) — start from the encrypted /api/settings agent_settings payload, drop schema_version and mcp_config (to avoid MCP-connection failures at creation time), merge in the exec tool set and load_*_skills flags, and send with secrets_encrypted: true. This is the pattern for real delegated work.
Template (full tools, preserves context):
set -euo pipefail
BASE="${AGENT_CANVAS_BACKEND:-http://localhost:8001}"
KEY="${SESSION_API_KEY:-${OH_SESSION_API_KEYS_0:-${LOCAL_BACKEND_API_KEY:-}}}"
if [ -z "$KEY" ] && [ -f "$HOME/.openhands/agent-canvas/api-key.txt" ]; then
KEY="$(tr -d '\n' < "$HOME/.openhands/agent-canvas/api-key.txt")"
fi
test -n "$KEY" || { echo "No Agent Canvas session API key found" >&2; exit 1; }
WORKDIR="${WORKDIR:-$HOME/workspace/delegated/$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$WORKDIR"
# Fetch the agent_settings with ENCRYPTED secrets exposed. This returns the
# real llm.api_key as a Fernet token (gAAAAA...) plus the existing
# agent_context/agent kind, so we preserve the caller's config and never
# handle plaintext credentials.
SETTINGS_JSON="$(curl -sS -H "X-Session-API-Key: $KEY" -H "X-Expose-Secrets: encrypted" "$BASE/api/settings")"
PROMPT='Write a complete, task-specific prompt here. Include repo, branch, constraints, validation, and expected report.'
PAYLOAD="$(jq -n --argjson settings "$SETTINGS_JSON" --arg prompt "$PROMPT" --arg workdir "$WORKDIR" '
# Start from the encrypted agent_settings so llm.api_key (Fernet token),
# agent_kind, and agent_context are preserved. Drop schema_version and
# mcp_config (MCP servers can fail to connect at creation time; the profile
# can be re-resolved later if needed).
def base_agent_settings:
($settings.agent_settings // {})
| del(.schema_version)
| del(.mcp_config);
# Merge the exec tool set into the existing tools list. Include task_tool_set
# when sub-agents are enabled — enable_sub_agents alone does not expose the
# delegation tool; Agent Canvas adds task_tool_set for that.
def with_tools:
.tools = ((.tools // []) + [
{name: "terminal", params: {}},
{name: "file_editor", params: {}},
{name: "task_tracker", params: {}},
{name: "browser_tool_set", params: {}},
{name: "canvas_ui", params: {}}
] + (if .enable_sub_agents then [{name: "task_tool_set", params: {}}] else [] end)
| unique_by(.name));
# Preserve the existing agent_context and enable skill loading for the
# delegated agent (defaults are false, so set these explicitly).
def with_skill_loading:
.agent_context = ((.agent_context // {}) + {
load_public_skills: true,
load_user_skills: true,
load_project_skills: true
});
($settings.conversation_settings // {}) as $conv |
{
secrets_encrypted: true,
agent_settings: (base_agent_settings | with_tools | with_skill_loading),
tool_module_qualnames: { canvas_ui: "canvas_ui_tool" },
workspace: {kind: "LocalWorkspace", working_dir: $workdir},
confirmation_policy: {kind: "NeverConfirm"},
# Delegated tasks usually need more than the SDK default of 80 iterations;
# default to the caller's conversation_settings value (1000 in Agent Canvas)
# so long-running tasks aren't cut off prematurely. Override per-task if needed.
max_iterations: (($conv.max_iterations // 1000) | if . == null then 1000 else . end),
stuck_detection: true,
autotitle: true,
worktree: false,
initial_message: {
role: "user",
content: [{type: "text", text: $prompt}],
run: true
}
}
')"
curl -sS -X POST "$BASE/api/conversations" \
-H "Content-Type: application/json" \
-H "X-Session-API-Key: $KEY" \
--data-binary "$PAYLOAD" | jq '{id, title, execution_status, workspace}'
Verify the new conversation actually has tools and is running (not errored):
CID="<conversation_id>"
curl -sS -H "X-Session-API-Key: $KEY" "$BASE/api/conversations/$CID" \
| jq '{execution_status, tools: [.agent.tools[]?.name]}'
curl -sS -H "X-Session-API-Key: $KEY" "$BASE/api/conversations/$CID/events/search?limit=20" \
| jq '[.events[]? | select(.kind=="ConversationErrorEvent") | .code] // []'
execution_status should be running/idle/finished (not error), tools should list the exec tools, and there should be no ConversationErrorEvent.
If MCP servers configured in the profile are unreachable, conversation creation can fail with MCP Connection Failure; the template drops mcp_config from the forwarded agent_settings to avoid that.
Report both links:
http://localhost:8000/conversations/<conversation_id>http://localhost:8001/api/conversations/<conversation_id>CID="<conversation_id>"
curl -sS -H "X-Session-API-Key: $KEY" "$BASE/api/conversations/$CID" \
| jq '{id, title, execution_status, updated_at, workspace, agent_kind: .agent.kind, current_model_id, current_model_name}'
curl -sS -H "X-Session-API-Key: $KEY" "$BASE/api/conversations/$CID/events/search?limit=20" \
| jq '.events // .items // .'
Terminal statuses commonly include idle, running, finished, error, stuck, and stopped.
Include:
Do not rely on the new conversation knowing anything from the current thread.
tools
Iterate on a GitHub pull request — drive it through CI, code review, and QA until it is merge-ready. Poll verification layers with `gh` CLI, diagnose and fix CI failures, address review feedback, retry flaky checks, push fixes, and repeat. The agent is the orchestration loop.
development
Guides technical explanations toward flowing, direct, conversational prose. This skill should be used for engineering chat, design discussion, architecture analysis, code-review explanations, and technical recommendations that should be concise without becoming fragmented or vague.
tools
This skill should be used when the user asks to "set up a Jira automation to create pull requests", "poll Jira for create-pr issues", "automatically create GitHub PRs from Jira tickets", "deploy a Jira issue-to-PR automation", "create a Jira to GitHub PR workflow", or mentions automating GitHub PR creation from a Jira label. Deploys a cron-based OpenHands automation that watches a Jira Cloud project for issues labeled with a configurable label (default: "create-pr") and spawns an agent conversation to create a GitHub pull request for each new issue found. The target GitHub repository is read from the body of the Jira ticket - no repo parameter is required at deploy time.
tools
This skill should be used when the user asks to "create an automation", "schedule a task", "set up a cron job", "webhook integration", "event-triggered automation", or mentions automations, scheduled tasks, cron scheduling, or webhook events in OpenHands Cloud.