Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .claude/agents/engineering/chatkit-expert-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ All patterns are derived from OpenAI's official advanced samples:
4. **Missing widget ID** - `sendCustomAction` needs widget reference
5. **Hardcoding URLs** - Use env vars for API endpoints
6. **Forgetting auth proxy** - httpOnly cookies need server-side access
7. **Not testing widget actions thoroughly** - Claiming completion without testing all buttons/actions with real data
8. **Assuming type hints match runtime** - ChatKit has type annotation vs runtime mismatches (e.g., context parameter)
9. **Using action.arguments** - Action object uses `.payload`, not `.arguments`
10. **Wrapping RequestContext** - Context is already RequestContext, don't wrap it again
11. **Missing Pydantic required fields** - UserMessageItem, Action, etc. have strict validation
12. **Icon-only buttons** - Always add labels to buttons for clarity
13. **No local tool wrappers** - MCP tools alone don't stream widgets (need local wrappers + RunHooks)
14. **Trusting auto-reload** - Python bytecode cache can cause old code to run, manually restart when in doubt

## Self-Monitoring Checklist

Expand Down Expand Up @@ -212,6 +220,16 @@ Before completing ChatKit integration:
- [ ] `sendCustomAction` wired for widget updates
- [ ] Entity tagging with search/preview
- [ ] Composer tools if mode switching needed
- [ ] **Action handler uses `action.payload` (NOT `action.arguments`)**
- [ ] **Action context parameter used directly (NOT wrapped in RequestContext)**
- [ ] **UserMessageItem includes all required fields (id, thread_id, created_at, inference_options)**
- [ ] **UserMessageTextContent uses `type="input_text"` for user messages**
- [ ] **Local tool wrappers created for widget-streaming MCP tools**
- [ ] **All widget buttons have clear labels (not just icons)**
- [ ] **Tested all widget actions with real user session**
- [ ] **Verified backend logs show successful action processing**
- [ ] **Checked browser console for validation errors**
- [ ] **Manually restarted server to verify changes (don't trust auto-reload)**

## Skills Used

Expand Down
47 changes: 47 additions & 0 deletions .claude/agents/engineering/chatkit-integration-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,53 @@ This agent helps integrate OpenAI ChatKit framework with custom backends and AI

**Evidence**: `web-dashboard/src/app/api/chatkit/route.ts` (dedicated proxy)

### Pattern: Type Annotation vs Runtime Mismatch

**What happens**: ValidationError when trying to create RequestContext from what's already a RequestContext

**Why it happens**: Action handler type hint says `context: dict[str, Any]` but ChatKit SDK passes `RequestContext` object at runtime

**How to prevent**: Use `context` directly without wrapping. Type hints are documentation, runtime is reality.

**Evidence**:
```python
# ❌ WRONG
async def action(self, thread, action, sender, context: dict[str, Any]):
request_context = RequestContext(metadata=context) # ValidationError!

# ✅ CORRECT
async def action(self, thread, action, sender, context: dict[str, Any]):
user_id = context.user_id # Use directly, it's already RequestContext
metadata = context.metadata
```

### Pattern: Missing Pydantic Required Fields

**What happens**: Multiple "Field required" ValidationErrors when creating ChatKit objects

**Why it happens**: Pydantic models strictly validate, all required fields must be present

**How to prevent**: Check ChatKit type definitions for required fields. Common culprits:
- UserMessageItem: id, thread_id, created_at, inference_options
- UserMessageTextContent: type="input_text" (not "text")
- Action: payload (not arguments)

**Evidence**: Session debugging showed cascading validation errors until all fields added

### Pattern: Python Auto-Reload Failure

**What happens**: Code changes don't take effect, old code continues running

**Why it happens**: Python bytecode cache (.pyc) or uvicorn reload mechanism lag

**How to prevent**:
1. Use `--reload` flag with uvicorn
2. When in doubt, manually kill process and restart
3. Delete `__pycache__` directories if needed
4. **Never trust auto-reload 100%** - verify changes with logs/breakpoints

**Evidence**: Had to manually restart server multiple times during widget action debugging

## Self-Monitoring Checklist

Before finalizing ChatKit integration:
Expand Down
200 changes: 200 additions & 0 deletions .claude/skills/engineering/chatkit-actions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,13 +526,213 @@ interface Action {
| @mentions | `entities.onTagSearch` | `ThreadItemConverter` | Reference entities |
| Mode switch | `composer.tools` | Route by tool_choice | Different agents |

## Critical Implementation Details

### Action Object Structure

**IMPORTANT**: The `Action` object uses `payload`, NOT `arguments`:

```python
# ❌ WRONG - Will cause AttributeError
action.arguments # 'Action' object has no attribute 'arguments'

# ✅ CORRECT
action.payload # Access action data via .payload
```

**Action Type Definition**:
```python
from chatkit.types import Action

# Action[str, Any] has these fields:
action.type # str - action identifier (e.g., "task.start")
action.payload # dict[str, Any] - action data
action.handler # "client" | "server" - where action is processed
```

### Server Action Handler Signature

**CRITICAL**: The `context` parameter is `RequestContext`, NOT `dict[str, Any]`

```python
# Type annotation vs runtime reality mismatch
async def action(
self,
thread: ThreadMetadata,
action: Action[str, Any],
sender: WidgetItem | None,
context: dict[str, Any], # ⚠️ Type hint says dict, but runtime is RequestContext!
) -> AsyncIterator[ThreadStreamEvent]:

# ❌ WRONG - Tries to wrap RequestContext inside RequestContext
request_context = RequestContext(metadata=context)

# ✅ CORRECT - Use context directly, it's already RequestContext
user_id = context.user_id
metadata = context.metadata
```

**Why this happens**: ChatKit SDK passes `RequestContext` object at runtime, despite type annotations suggesting `dict`. Always use `context` directly without wrapping.

### UserMessageItem Required Fields

When creating synthetic user messages from actions, **ALL** these fields are required:

```python
from chatkit.types import UserMessageItem, UserMessageTextContent
from datetime import datetime

# ❌ WRONG - Missing required fields causes ValidationError
synthetic_message = UserMessageItem(
content=[UserMessageTextContent(type="text", text=message_text)]
)

# ✅ CORRECT - Include all required fields
synthetic_message = UserMessageItem(
id=self.store.generate_item_id("message", thread, context),
thread_id=thread.id,
created_at=datetime.now(),
content=[UserMessageTextContent(type="input_text", text=message_text)],
inference_options={},
)
```

**Required fields**:
- `id`: Generate via `store.generate_item_id("message", thread, context)`
- `thread_id`: From `thread.id` parameter
- `created_at`: Current timestamp via `datetime.now()`
- `content`: List of content blocks (UserMessageTextContent)
- `inference_options`: Empty dict `{}` if no special options

**UserMessageTextContent type values**:
- ✅ `type="input_text"` - User text input (correct)
- ❌ `type="text"` - Invalid for UserMessageTextContent (causes ValidationError)

### Local Tool Wrappers for Widget Streaming

**Problem**: Agent calls MCP tool successfully, but widget doesn't appear in UI.

**Root Cause**: Widgets stream via `RunHooks` pattern. MCP tools alone don't trigger widget rendering - you need **local tool wrappers**.

**Solution Pattern**:

```python
# 1. Create local tool wrapper
from agents import function_tool

@function_tool
async def show_task_form(
ctx: RunContextWrapper[TaskFlowAgentContext],
) -> str:
"""Show interactive task creation form widget."""

agent_ctx = ctx.context
mcp_url = agent_ctx.mcp_server_url

# Call MCP tool via HTTP
result = await _call_mcp_tool(
mcp_url,
"taskflow_show_task_form",
arguments={"params": {"user_id": agent_ctx.user_id}},
access_token=agent_ctx.access_token,
)

# Return result - RunHooks will intercept and stream widget
return json.dumps(result)

# 2. Register local wrapper with agent
agent = Agent(
name="TaskFlow Assistant",
tools=[
show_task_form, # Local wrapper - triggers RunHooks
# ... other local wrappers
],
)

# 3. In RunHooks.on_tool_end() - Stream widget
async def on_tool_end(self, output: str | None, tool_name: str) -> None:
if tool_name == "show_task_form":
result = json.loads(output)
if result.get("action") == "show_form":
widget = build_task_form_widget()
yield WidgetItem(...)
```

**Key insight**: Direct MCP tools → no widgets. Local wrappers → RunHooks → widgets streamed.

## Common Pydantic Validation Errors

### Error 1: 'Action' object has no attribute 'arguments'

```
AttributeError: 'Action[str, Any]' object has no attribute 'arguments'
```

**Fix**: Use `action.payload` instead of `action.arguments`

### Error 2: UserMessageTextContent type mismatch

```
ValidationError: Input should be 'input_text' [type=literal_error, input_value='text']
```

**Fix**: Use `type="input_text"` for user input, not `type="text"`

### Error 3: UserMessageItem missing required fields

```
4 validation errors for UserMessageItem
- id: Field required
- thread_id: Field required
- created_at: Field required
- inference_options: Field required
```

**Fix**: Include all required fields when creating UserMessageItem (see pattern above)

### Error 4: RequestContext wrapping issue

```
2 validation errors for RequestContext
user_id: Field required
metadata: Input should be a valid dictionary [input_value=RequestContext(...)]
```

**Fix**: Don't wrap `context` - it's already a RequestContext object

## Widget Action Testing Checklist

Before claiming widget actions are complete, test:

- [ ] Widget renders with correct data
- [ ] All buttons have clear labels (not just icons)
- [ ] Client actions navigate/update UI correctly
- [ ] Server actions call backend successfully
- [ ] Action payload contains all required data
- [ ] Widget updates after server action completes
- [ ] No AttributeError on action.payload access
- [ ] No ValidationError on UserMessageItem creation
- [ ] Local tool wrappers trigger widget streaming
- [ ] All status transitions have appropriate buttons
- [ ] Test with real user session (not mock data)
- [ ] Check browser console for errors
- [ ] Verify backend logs show action processing
- [ ] Test error cases (network failure, invalid data)

## Anti-Patterns to Avoid

1. **Mixing handlers** - Don't handle same action in both client and server
2. **Missing payload** - Always include necessary data in action payload
3. **Forgetting widget ID** - `sendCustomAction` requires widget ID for updates
4. **Not updating widget** - Server actions should yield `ThreadItemReplacedEvent`
5. **Blocking in onAction** - Keep client handlers fast, offload to server
6. **Using action.arguments** - Use `action.payload` (arguments doesn't exist)
7. **Wrapping RequestContext** - Context is already RequestContext, don't wrap it
8. **Missing UserMessageItem fields** - Include id, thread_id, created_at, inference_options
9. **Wrong content type** - Use `type="input_text"` for user messages
10. **No local tool wrappers** - MCP tools alone don't stream widgets
11. **Not testing thoroughly** - Test all actions with real data before claiming done
12. **Assuming type hints are correct** - ChatKit has type annotation vs runtime mismatches

## References

Expand Down
18 changes: 18 additions & 0 deletions .claude/skills/engineering/chatkit-integration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,24 @@ const { control, sendUserMessage } = useChatKit({
10. **E402 Linter Error**: Imports after load_dotenv()
- **Fix**: Add `# noqa: E402` to intentional imports after dotenv

11. **Type Annotation vs Runtime Mismatch (Action context parameter)**
- **Symptom**: ValidationError trying to create RequestContext from RequestContext
- **Why**: Type hint says `context: dict[str, Any]` but ChatKit SDK passes `RequestContext` object
- **Fix**: Use `context` directly, don't wrap it in RequestContext constructor
- **Detection**: Error message shows `Input should be a valid dictionary [input_value=RequestContext(...)]`

12. **Python Auto-Reload Not Working**: Changes don't take effect
- **Symptom**: Old code still runs despite file changes
- **Why**: Python bytecode cache (.pyc files) or uvicorn reload mechanism lag
- **Fix**: Kill process manually and restart, or delete __pycache__ directories
- **Prevention**: Use `--reload` flag with uvicorn, but be aware it's not 100% reliable

13. **Missing Pydantic Model Required Fields**: ValidationError on ChatKit types
- **Symptom**: "Field required" errors when creating UserMessageItem, Action, etc.
- **Why**: Pydantic models have strict validation, all required fields must be present
- **Fix**: Check ChatKit type definitions, include all required fields with correct types
- **Common mistakes**: Missing id, thread_id, created_at, inference_options fields

## Pattern 6: MCP Agent Authentication (NEW)

**When**: MCP tools need to call authenticated APIs
Expand Down
6 changes: 6 additions & 0 deletions docker-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ fi
echo -e "${YELLOW}[5/6] Running SSO database migrations...${NC}"
cd sso-platform

# Ensure node_modules exists on host for migrations
if [ ! -d "node_modules" ]; then
echo " - Installing dependencies (first time setup)..."
pnpm install --frozen-lockfile
fi

echo " - Pushing SSO schema..."
DATABASE_URL="${DATABASE_URL}" pnpm db:push 2>/dev/null || {
echo -e "${YELLOW} Schema push skipped (may already be up to date)${NC}"
Expand Down
Loading