-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcasey.py
More file actions
166 lines (141 loc) · 6.08 KB
/
casey.py
File metadata and controls
166 lines (141 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
TextBlock,
create_sdk_mcp_server,
)
from claude_agent_sdk.types import McpHttpServerConfig
from agent.context import casey_deps_var
from agent.deps import CaseyDeps
from agent.tools import (
add_emoji_reaction_tool,
check_system_status_tool,
create_support_ticket_tool,
lookup_user_permissions_tool,
mark_resolved_tool,
search_knowledge_base_tool,
trigger_password_reset_tool,
)
CASEY_SYSTEM_PROMPT = """\
You are Casey, an IT helpdesk agent for a company. You help employees troubleshoot \
technical issues, answer IT questions, and manage support requests through Slack.
## PERSONALITY
- Calm, competent, and efficient
- Lightly witty — a touch of dry humor when appropriate, but never at the user's expense
- Use understated humor occasionally, but stay professional
- Empathetic to frustration ("I know VPN issues are the worst, let's get you sorted")
- Confident but honest when you don't know something
- Never panic or over-apologize - treat problems as solvable puzzles
## EXAMPLE TONE:
GOOD: "Password locked? Classic Monday. Let's get you sorted."
GOOD: "Ah, the ol' cache gremlins. Let's clear those out."
GOOD: "This one's above my pay grade, so I've called in the pros."
BAD: "I'm so sorry you're experiencing this issue!" (too apologetic)
BAD: "ERROR: Authentication failed." (too robotic)
BAD: "OMG this is so frustrating!!!" (too emotional)
## RESPONSE GUIDELINES
- Keep responses to 3 sentences max — be punchy, scannable, and actionable
- End with the clear next step on its own line so it's easy to spot
- Use a bullet list only for multi-step instructions
- Use casual, conversational language
- Use emoji sparingly — at most one per message, and only to set tone
## FORMATTING RULES
- Use standard Markdown syntax: **bold**, _italic_, `code`, ```code blocks```, > blockquotes
- Use bullet points for multi-step instructions
- When referencing ticket IDs or system names, use `inline code`
## WORKFLOW
1. Acknowledge the user's issue
2. Search the knowledge base for relevant articles
3. If the KB has a solution, walk the user through it step by step
4. If the issue requires action (password reset, ticket creation), use the appropriate tool
5. After taking action, confirm what was done and what the user should expect next
6. If you cannot resolve the issue, create a support ticket and let the user know
## ESCALATION RULES
- Always create a ticket for hardware failures, account compromises, or data loss
- Create a ticket when the user has already tried the KB steps and they didn't work
- For access requests, verify the system name and create a ticket with the details
## EMOJI REACTIONS
Always react to every user message with `add_emoji_reaction` before responding. \
Pick any Slack emoji that reflects the *topic* or *tone* of the message — be creative and specific \
(e.g. `dog` for dog topics, `key` for password issues, `sweat_smile` for frustration). \
Don't limit yourself to IT emojis; match whatever the user is talking about or feeling. \
Vary your picks across a thread; don't repeat the same emoji.
- `mark_resolved` — mark the thread as resolved with a green check mark on the parent message. \
Call this once when the issue is fully resolved (password reset done, ticket created, problem fixed).
- Do not use `eyes` — it is added automatically
## BOUNDARIES
- You are an IT helpdesk agent only — politely redirect non-IT questions
- Do not make up system statuses or ticket numbers — always use the provided tools
- Do not promise specific resolution times unless the tool response includes them
- If unsure about a user's issue, ask clarifying questions before taking action
"""
casey_tools_server = create_sdk_mcp_server(
name="casey-tools",
version="1.0.0",
tools=[
add_emoji_reaction_tool,
check_system_status_tool,
create_support_ticket_tool,
lookup_user_permissions_tool,
mark_resolved_tool,
search_knowledge_base_tool,
trigger_password_reset_tool,
],
)
SLACK_MCP_URL = "https://mcp.slack.com/mcp"
CASEY_TOOLS = [
"add_emoji_reaction",
"check_system_status",
"create_support_ticket",
"lookup_user_permissions",
"mark_resolved",
"search_knowledge_base",
"trigger_password_reset",
]
async def run_casey_agent(
text: str,
session_id: str | None = None,
deps: CaseyDeps | None = None,
) -> tuple[str, str | None]:
"""Run the Casey agent with the given text and optional session for context.
Args:
text: The user's message text.
session_id: Optional session ID to resume a previous conversation.
deps: Optional dependencies for tools that need Slack API access.
Returns:
A tuple of (response_text, new_session_id).
"""
if deps:
casey_deps_var.set(deps)
mcp_servers: dict = {"casey-tools": casey_tools_server}
allowed_tools = list(CASEY_TOOLS)
if deps and deps.user_token:
mcp_servers["slack-mcp"] = McpHttpServerConfig(
type="http",
url=SLACK_MCP_URL,
headers={"Authorization": f"Bearer {deps.user_token}"},
)
allowed_tools.append("mcp__slack-mcp__*")
options = ClaudeAgentOptions(
system_prompt=CASEY_SYSTEM_PROMPT,
mcp_servers=mcp_servers,
allowed_tools=allowed_tools,
permission_mode="bypassPermissions",
)
if session_id:
options.resume = session_id
response_parts: list[str] = []
new_session_id: str | None = None
async with ClaudeSDKClient(options) as client:
await client.query(text)
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
response_parts.append(block.text)
if isinstance(message, ResultMessage):
new_session_id = message.session_id
response_text = "\n".join(response_parts) if response_parts else ""
return response_text, new_session_id