Open or resume a helpdesk ticket. Tickets are independent of projects.
Always prefix active workspace, project, or ticket names with a 🟢 emoji. This applies everywhere an active/selected name is displayed — auto-select messages, confirmation prompts, resume prompts, and list items.
Example:
You were last working on 🟢 Fix login bug. Would you like to pick up where you left off?
In lists, only the currently active item gets the 🟢 prefix.
Follow these steps in order:
Read .duplocloud/state.toon (file may not exist). Note workspace_id, active_ticket_name, project_id, project_name if present.
Call duplo-helpdesk::Workspaces_get_available to get the list of available workspaces.
If the call returns an empty list, tell the user:
"It looks like you don't have any workspaces set up yet. Please create one in the DuploCloud portal first." Then stop.
If only one workspace is returned: auto-select it and tell the user:
"Auto-selecting 🟢 <workspace name> — it's the only workspace available." Capture its
idasworkspace_id.
If multiple workspaces and workspace_id is present in state:
workspace_id exists in the returned list."You're currently connected to 🟢 <workspace name>. Would you like to continue with this workspace? (y/n)"
If workspace_id is absent from state: show the workspace list (Step 1a).
Show a numbered list using only the workspace name (no raw IDs). If a workspace matches the workspace_id currently in state, prefix it with 🟢:
Here are your available workspaces:
1. 🟢 <name> ← currently active
2. <name>
...Ask: "Which workspace would you like to use?"
Capture the selected workspace's id as workspace_id (store internally, do not show to the user).
If the newly selected workspace differs from what was in state: clear active_ticket_name (the old ticket belongs to the old workspace).
After workspace is resolved (whether auto-selected or chosen via Step 1a):
If DUPLO_AGENT_MODE=false or not set (local agent mode): call these in parallel (one tool-call group):
duplo-helpdesk::Ticket_list with workspaceId = workspace_idduplo-helpdesk::Workspaces_get_personas with id = workspace_idduplo-helpdesk::Workspaces_get_scopes with id = workspace_idduplo-helpdesk::Workspaces_get_skills with id = workspace_id — workspace-specific skills via personasduplo-helpdesk::Workspaces_get_skills_built_in_files (no parameters) — platform built-in skill filesduplo-helpdesk::Projects_get with id = project_id ← only if project_id is in stateWrite all of the following in the single end-of-step Bash flush:
.duplocloud/state.toon — with tickets array~/.duplocloud/workspace_context.json — using the personas, scopes, skills, and project data; set fetched_at to now (ISO 8601 UTC). See Local Agent Context section in CLAUDE.md for schema.~/.duplocloud/skills/<skillName>/<path> — for each entry from Workspaces_get_skills_built_in_files write content to the corresponding path. For each workspace skill with non-empty skillMd write to ~/.duplocloud/skills/<name>/SKILL.md. Create ~/.duplocloud/skills/ directory first.If DUPLO_AGENT_MODE=true (remote agent): call only duplo-helpdesk::Ticket_list with workspaceId = workspace_id.
Store the results in .duplocloud/state.toon under a tickets field:
tickets[N]{name,title,status,aiAgentId}:
<ticketName>,<title>,<status>,<aiAgentId or null>
...Write the updated state silently. Step 3 will use this cached list.
If active_ticket_name is present in state (and workspace has not changed), look up the ticket title from the cached tickets list. Ask the user:
"You were last working on 🟢 <title>. Would you like to pick up where you left off? (y/n)"
If absent: continue to Step 3.
Use the cached tickets array from state. Filter to open or inProgress status only.
Display using the ticket title as the primary label. Map status values to friendly labels: open → "Open", inProgress → "In Progress". Do not show raw ticket name IDs.
If a ticket matches the active_ticket_name currently in state, prefix it with 🟢:
Here are your open tickets:
1. 🟢 <title> — In Progress ← currently active
2. <title> — Open
...
N+1. Start a new ticketIf there are no open tickets, skip the list and go directly to Step 4.
Ask: "Which would you like to work on?"
name as active_ticket_name, go to Step 5.If project_id is present in state:
Call duplo-helpdesk::Projects_get with id = project_id.
From the response extract:
has_execution_tasks = true if any stage in execution.stages has tasksIf has_execution_tasks is true, ask the user:
"Your project <project_name> has tasks ready to execute. What would you like to do?
- Work on a project task
- Open a standalone ticket"
If has_execution_tasks is false, or project_id is absent: go to Step 4c.
Using the stages data from the project response, count total tasks across all stages.
If total tasks > 10 — first ask user to select a stage (show name and task count, no IDs):
Which area would you like to work on?
1. <stage_name> — <stage_description> (<N> tasks)
2. ...Then show tasks within the chosen stage:
Which task would you like to work on?
1. <task_title> — <friendly ticket status or "No ticket yet">
2. ...If total tasks ≤ 10 — show all tasks flat:
Which task would you like to work on?
1. <task_title> (<stage_name>) — <friendly ticket status or "No ticket yet">
2. ...Wait for task selection. Capture task_id (the task's name UUID, stored internally) and task_title.
If the task already has a ticket (ticket_name present in the task data):
active_ticket_name to that ticket name.If no ticket yet: continue to Step 4d (select agent), then Step 4e.
Ask the user: "What should we call this ticket?"
Call duplo-helpdesk::Workspaces_get_agents with id = workspace_id (always fetch — needed in both modes).
If DUPLO_AGENT_MODE=false or not set (local agent mode): auto-select silently — do NOT prompt the user:
id. Tell the user: "Auto-selecting agent 🟢 <name>."id. Tell the user: "Auto-selecting agent 🟢 <name>."selected_agent_id = null.If DUPLO_AGENT_MODE=true: Show a numbered list using agent name only (no IDs or endpoints):
Which agent should handle this ticket?
1. <name>
2. ...Call duplo-helpdesk::Workspaces_get_personas with id = workspace_id (always fetch fresh from backend).
selected_persona_ids = [] and proceed to Step 4e."Auto-selecting persona 🟢 <name> — it's the only one available." Set
selected_persona_ids = [<that persona's id>]. Proceed to Step 4e.
Which personas should have access to this ticket? (enter comma-separated numbers, or press Enter to skip)
1. <name>
2. <name>
...1,3) → capture the IDs of the selected personas as selected_persona_ids.selected_persona_ids = [].Standalone ticket — call duplo-helpdesk::Ticket_create with workspaceId = workspace_id and body:
{
"title": "<user-provided title>",
"aiAgentId": "<selected_agent_id — omit this field if null>",
"workspaceId": "<workspace_id>",
"origin": "api",
"ticketContextForAgent": {
"personaIds": ["<selected_persona_ids>"]
}
}Omit aiAgentId entirely if selected_agent_id is null. Omit ticketContextForAgent entirely if selected_persona_ids is empty.
Spec or plan creation ticket (when project_ticket_type is set) — call duplo-helpdesk::Ticket_create with workspaceId = workspace_id and body:
{
"title": "<project_name> — <spec_creation or plan_creation>",
"aiAgentId": "<selected_agent_id — omit this field if null>",
"workspaceId": "<workspace_id>",
"origin": "api",
"ticketContextForAgent": {
"personaIds": ["<selected_persona_ids>"]
},
"originContext": {
"type": "Project",
"id": "<project_id>",
"subType": "<project_ticket_type>"
}
}Omit aiAgentId entirely if selected_agent_id is null. Omit ticketContextForAgent entirely if selected_persona_ids is empty.
Execution task ticket — call duplo-helpdesk::Ticket_create with workspaceId = workspace_id and body:
HARD RULE: taskId MUST be inside originContext.metadata. Never place taskId as a top-level field. The ticket will NOT be linked to the task if the structure is wrong.
{
"title": "<task_title>",
"aiAgentId": "<selected_agent_id — omit this field if null>",
"workspaceId": "<workspace_id>",
"origin": "api",
"ticketContextForAgent": {
"personaIds": ["<selected_persona_ids>"]
},
"originContext": {
"type": "Project",
"id": "<project_id>",
"subType": "execution",
"metadata": {
"taskId": "<task_id>",
"projectType": "plan_execution"
}
}
}Omit aiAgentId entirely if selected_agent_id is null. Omit ticketContextForAgent entirely if selected_persona_ids is empty.
Capture the returned ticket's name as active_ticket_name (stored internally).
Call duplo-helpdesk::Ticket_get_messages with workspaceId = workspace_id, ticketName = active_ticket_name.
Write .duplocloud/state.toon silently, preserving any existing project_id and project_name fields:
workspace_id: <workspace_id>
project_id: <project_id if present, else omit>
project_name: <project_name if present, else omit>
active_ticket_name: <active_ticket_name>
tickets[N]{name,title,status,aiAgentId}:
<row per ticket>HARD RULES — apply for the entire duration this ticket is active:
open state. Do NOT call Ticket_put_status automatically.duplo-helpdesk::Ticket_put_status with { "status": "inProgress" } before forwarding the message to the agent.duplo-helpdesk::Ticket_put_status with { "status": "closed", "disposition": "resolved" }. Remove active_ticket_name from state. Tell the user the ticket has been closed.Do NOT close the ticket automatically for any other reason. Only close when the user explicitly confirms they are done with the activity on this ticket.
HARD RULES — apply for the entire duration this ticket is active:
open state. Do NOT call Ticket_put_status automatically.duplo-helpdesk::Ticket_put_status with { "status": "inProgress" } before forwarding the message to the agent.duplo-helpdesk::Ticket_put_status with { "status": "closed", "disposition": "resolved" }. Remove active_ticket_name from state. Tell the user the ticket has been closed.Do NOT close the ticket automatically for any other reason. Only close when the user explicitly confirms they are done with the activity on this ticket.
If DUPLO_AGENT_MODE=false or not set (local agent mode): skip this step entirely. Claude handles all responses directly — no remote agent assignment needed.
If DUPLO_AGENT_MODE=true:
Call duplo-helpdesk::Workspaces_get_agents with id = workspace_id.
From the ticket data (captured in Step 3 or Step 4e), note the aiAgentId.
Context: Once a ticket is active, the assigned agent handles all your messages. Claude Code only manages ticket lifecycle (activation, creation, closing, spec and plan writing).
If the ticket already has an agent assigned:
Resolve the agent name from the agents list. Tell the user:
"<agent name> is assigned to this ticket and will respond to your messages. Would you like to continue with this agent or switch?
- Continue with <agent name>
- Switch agent"
If no agent is assigned:
Tell the user: "No agent is assigned to this ticket yet. Please pick one to handle your messages:" Go to Step 7b-select.
Show agent names only (no IDs or endpoints):
1. <name>
2. ...Ask: "Which agent should handle your messages?"
Call duplo-helpdesk::Ticket_put_assignee with workspaceId = workspace_id, ticketName = active_ticket_name, agentId = <selected agent id>.
Tell the user: "<agent name> is now assigned."
Then tell the user:
"Ticket 🟢 <title> is ready. <agent name> will respond to your messages."
6a46510
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.