Back to skills
SkillHub ClubShip Full StackFull StackFrontend

castella-agent-ui

Build chat interfaces and agent management UIs with Castella. Create chat components, display tool calls, manage multiple agents, and build agent hubs.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
39
Hot score
90
Updated
March 20, 2026
Overall rating
C2.1
Composite score
2.1
Best-practice grade
B73.2

Install command

npx @skill-hub/cli install i2y-castella-castella-agent-ui

Repository

i2y/castella

Skill path: skills/castella-agent-ui

Build chat interfaces and agent management UIs with Castella. Create chat components, display tool calls, manage multiple agents, and build agent hubs.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Frontend.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: i2y.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install castella-agent-ui into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/i2y/castella before adding castella-agent-ui to shared team environments
  • Use castella-agent-ui for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: castella-agent-ui
description: Build chat interfaces and agent management UIs with Castella. Create chat components, display tool calls, manage multiple agents, and build agent hubs.
---

# Castella Agent UI Components

High-level components for building conversational interfaces and agent management UIs.

**When to use**: "create a chat UI", "AgentChat", "chat with agent", "display tool calls", "multi-agent chat", "AgentHub", "message history", "MultiAgentChat"

## Quick Start (3 Lines)

Create a chat UI connected to an A2A agent:

```python
from castella.agent import AgentChat

chat = AgentChat.from_a2a("http://localhost:8080")
chat.run()
```

## Installation

```bash
uv sync --extra agent   # Agent UI + A2A + A2UI support
```

## AgentChat

High-level chat component with minimal setup:

```python
from castella.agent import AgentChat

# Connect to A2A agent
chat = AgentChat.from_a2a("http://localhost:8080")
chat.run()

# Or use custom handler function
chat = AgentChat(
    handler=lambda msg: f"Echo: {msg}",
    title="Echo Bot",
    system_message="Welcome! How can I help?",
)
chat.run()
```

### Parameters

```python
AgentChat(
    a2a_client=None,           # A2AClient instance
    handler=None,              # Custom handler: (str) -> str
    title="Agent Chat",        # Window title
    placeholder="Type...",     # Input placeholder
    system_message=None,       # Initial system message
    show_agent_card=True,      # Show agent card for A2A
    width=700,                 # Window width
    height=550,                # Window height
)
```

### Factory Methods

```python
# From A2A agent URL
chat = AgentChat.from_a2a("http://localhost:8080")

# From A2A client
from castella.a2a import A2AClient
client = A2AClient("http://localhost:8080")
chat = AgentChat.from_a2a(client)
```

## Chat Components

Build custom chat UIs with lower-level components.

### ChatContainer

Complete chat UI (messages + input):

```python
from castella.agent import ChatContainer, ChatMessageData
from castella.core import ListState

messages = ListState([])

def on_send(text: str):
    messages.append(ChatMessageData(role="user", content=text))
    # Get response from agent...
    response = get_response(text)
    messages.append(ChatMessageData(role="assistant", content=response))

container = ChatContainer(
    messages,
    on_send=on_send,
    title="My Chat",
    placeholder="Type a message...",
)
```

### ChatMessage

Display a single message:

```python
from castella.agent import ChatMessage, ChatMessageData

msg = ChatMessageData(
    role="assistant",  # "user", "assistant", or "system"
    content="Hello! How can I help you today?",
)
widget = ChatMessage(msg)
```

### ChatInput

Text input with send button:

```python
from castella.agent import ChatInput

input_widget = ChatInput(
    placeholder="Type a message...",
    on_send=lambda text: print(f"Sent: {text}"),
)
```

### ChatView

Scrollable message list:

```python
from castella.agent import ChatView
from castella.core import ScrollState

scroll_state = ScrollState()
view = ChatView(messages, scroll_state=scroll_state)
```

## ChatMessageData

Message data structure:

```python
from castella.agent import ChatMessageData, ToolCallData

msg = ChatMessageData(
    role="assistant",
    content="Let me check the weather for you.",
    tool_calls=[
        ToolCallData(
            id="call_1",
            name="get_weather",
            arguments={"location": "Tokyo"},
            result="Sunny, 22°C",
        )
    ],
)
```

## Tool Call Visualization

### ToolCallView

Display a single tool call:

```python
from castella.agent import ToolCallView

tool = ToolCallView(
    name="get_weather",
    arguments={"location": "Tokyo"},
    result="Sunny, 22°C",
)
```

### ToolHistoryPanel

Display history of tool calls:

```python
from castella.agent import ToolHistoryPanel
from castella.core import ListState

tool_calls = ListState([...])
panel = ToolHistoryPanel(tool_calls)
```

## Agent Card Display

### AgentCardView

Show agent information:

```python
from castella.agent import AgentCardView
from castella.a2a import A2AClient

client = A2AClient("http://agent.example.com")
card_view = AgentCardView(
    client.agent_card,
    show_skills=True,
    compact=False,
)
```

### AgentListView

Display multiple agents:

```python
from castella.agent import AgentListView

agent_list = AgentListView(
    agents=[client1.agent_card, client2.agent_card],
    on_select=lambda card: print(f"Selected: {card.name}"),
)
```

## MultiAgentChat

Tabbed interface for multiple agents:

```python
from castella.agent import MultiAgentChat
from castella.a2a import A2AClient

chat = MultiAgentChat({
    "weather": A2AClient("http://localhost:8081"),
    "travel": A2AClient("http://localhost:8082"),
    "restaurant": A2AClient("http://localhost:8083"),
})
chat.run()
```

Each agent gets its own chat tab with independent message history.

## AgentHub

Agent discovery and management dashboard:

```python
from castella.agent import AgentHub
from castella.a2a import A2AClient

# Create hub
hub = AgentHub(title="Agent Hub")

# Add agents
hub.add_agent("http://localhost:8081")
hub.add_agent(A2AClient("http://localhost:8082"))

hub.run()
```

Or initialize with agents:

```python
hub = AgentHub(agents=[
    A2AClient("http://agent1.example.com"),
    A2AClient("http://agent2.example.com"),
])
```

Features:
- Left panel: List of agents with add/remove
- Right panel: Chat with selected agent
- URL input to add new agents at runtime

## Scroll Position Pattern

Important pattern for chat UIs - set scroll before adding message:

```python
class ChatComponent(Component):
    def __init__(self):
        super().__init__()
        self._messages = ListState([])
        self._messages.attach(self)
        self._scroll_state = ScrollState()
        # DON'T attach scroll state

    def _send_message(self, text: str):
        # Add user message
        self._messages.append(ChatMessageData(role="user", content=text))

        # Get response...
        response = get_response(text)

        # Set scroll BEFORE adding response (so re-render picks it up)
        self._scroll_state.y = 999999
        self._messages.append(ChatMessageData(role="assistant", content=response))
```

## Lazy State Attachment

For components created before App exists (like AgentHub):

```python
class MyComponent(Component):
    def __init__(self):
        super().__init__()
        self._state = State(0)
        self._states_attached = False
        # DON'T attach here - App may not exist yet

    def view(self):
        # Attach lazily when view() is called
        if not self._states_attached:
            self._state.attach(self)
            self._states_attached = True
        return Text(str(self._state()))
```

## Message Markdown Support

Messages support Markdown formatting:

```python
msg = ChatMessageData(
    role="assistant",
    content="""
# Weather Report

**Tokyo**: Sunny, 22°C

| Day | High | Low |
|-----|------|-----|
| Mon | 24°C | 18°C |
| Tue | 22°C | 17°C |
""",
)
```

## Best Practices

1. **Use ScrollState without attaching** for chat scroll
2. **Set scroll position before adding** new messages
3. **Use Markdown** for rich message content
4. **Handle loading states** for async responses
5. **Use ListState.append()** for new messages
6. **Use lazy state attachment** for hub-style components

## Reference

- `references/components.md` - Component API reference
- `references/data_classes.md` - ChatMessageData, ToolCallData types
- `scripts/` - Executable examples (simple_chat.py, multi_agent.py, agent_hub.py)


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/components.md

```markdown
# Agent UI Components Reference

Complete API reference for Castella agent UI components.

## AgentChat

High-level chat component.

### Constructor

```python
from castella.agent import AgentChat

chat = AgentChat(
    a2a_client: A2AClient = None,     # A2A client
    handler: Callable[[str], str] = None,  # Custom handler
    title: str = "Agent Chat",        # Window title
    placeholder: str = "Type a message...",
    system_message: str = None,       # Initial message
    show_agent_card: bool = True,     # Show agent info
    width: int = 700,
    height: int = 550,
)
```

### Factory Methods

```python
# From URL
AgentChat.from_a2a(url: str) -> AgentChat

# From client
AgentChat.from_a2a(client: A2AClient) -> AgentChat
```

### Methods

```python
chat.run()  # Run the chat app (blocking)
```

## ChatContainer

Embeddable chat container.

### Constructor

```python
from castella.agent import ChatContainer
from castella.core import ListState

messages = ListState([ChatMessageData(...)])

container = ChatContainer(
    messages: ListState[ChatMessageData],
    on_send: Callable[[str], None],
    title: str = "Chat",
    placeholder: str = "Type a message...",
    show_header: bool = True,
)
```

### Properties

```python
container.messages   # ListState of messages
container.on_send    # Send callback
```

## ChatView

Scrollable message list.

### Constructor

```python
from castella.agent import ChatView

view = ChatView(
    messages: ListState[ChatMessageData],
    scroll_state: ScrollState = None,
)
```

## ChatMessage

Single message display.

### Constructor

```python
from castella.agent import ChatMessage

message = ChatMessage(
    data: ChatMessageData,
    compact: bool = False,
)
```

## ChatInput

Input field with send button.

### Constructor

```python
from castella.agent import ChatInput

input_widget = ChatInput(
    placeholder: str = "Type a message...",
    on_send: Callable[[str], None] = None,
)
```

## ToolCallView

Tool call display.

### Constructor

```python
from castella.agent import ToolCallView

tool = ToolCallView(
    name: str,
    arguments: dict,
    result: str = None,
    is_error: bool = False,
    compact: bool = False,
)
```

## ToolHistoryPanel

Tool call history.

### Constructor

```python
from castella.agent import ToolHistoryPanel

panel = ToolHistoryPanel(
    tool_calls: ListState[ToolCallData],
    title: str = "Tool Calls",
)
```

## AgentCardView

Agent information display.

### Constructor

```python
from castella.agent import AgentCardView

card_view = AgentCardView(
    agent_card: AgentCard,
    show_skills: bool = True,
    compact: bool = False,
    on_click: Callable[[AgentCard], None] = None,
)
```

## AgentListView

List of agents.

### Constructor

```python
from castella.agent import AgentListView

agent_list = AgentListView(
    agents: list[AgentCard],
    on_select: Callable[[AgentCard], None] = None,
    selected_index: int = -1,
)
```

### Methods

```python
agent_list.set_agents(agents: list[AgentCard])
agent_list.select(index: int)
```

## MultiAgentChat

Multi-agent tabbed chat.

### Constructor

```python
from castella.agent import MultiAgentChat

chat = MultiAgentChat(
    agents: dict[str, A2AClient],
    title: str = "Multi-Agent Chat",
    width: int = 800,
    height: int = 600,
)
```

### Methods

```python
chat.run()  # Run the app (blocking)
```

## AgentHub

Agent discovery dashboard.

### Constructor

```python
from castella.agent import AgentHub

hub = AgentHub(
    title: str = "Agent Hub",
    agents: list[A2AClient] = None,
    width: int = 1000,
    height: int = 700,
)
```

### Methods

```python
hub.add_agent(url_or_client: str | A2AClient)
hub.remove_agent(index: int)
hub.run()  # Run the app (blocking)
```

## Styling

### Message Colors

```python
# Default colors by role
"user": "#1e40af"      # Blue
"assistant": "#1a1b26" # Dark
"system": "#374151"    # Gray
```

### Custom Styling

```python
# Access underlying widgets for styling
message_widget = ChatMessage(data)
styled = message_widget.bg_color("#custom").padding(10)
```

## Event Callbacks

### on_send

Called when user sends a message:

```python
def on_send(text: str):
    print(f"User sent: {text}")
    # Return response or update messages
```

### on_select

Called when agent is selected (AgentListView):

```python
def on_select(card: AgentCard):
    print(f"Selected: {card.name}")
```

### on_click

Called when agent card is clicked:

```python
def on_click(card: AgentCard):
    open_chat_with(card)
```

```

### references/data_classes.md

```markdown
# Agent UI Data Classes

Data structures for chat messages and tool calls.

## ChatMessageData

Represents a chat message.

```python
from castella.agent import ChatMessageData

msg = ChatMessageData(
    role: str,                    # "user", "assistant", "system"
    content: str,                 # Message text (supports Markdown)
    tool_calls: list[ToolCallData] = None,  # Optional tool calls
    timestamp: datetime = None,   # Message time
    metadata: dict = None,        # Additional data
)
```

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `role` | str | Sender role |
| `content` | str | Message content |
| `tool_calls` | list | Tool call results |
| `timestamp` | datetime | When sent |
| `metadata` | dict | Extra data |

### Example

```python
# Simple message
msg = ChatMessageData(
    role="user",
    content="What's the weather in Tokyo?",
)

# Assistant with tool call
msg = ChatMessageData(
    role="assistant",
    content="Let me check that for you.",
    tool_calls=[
        ToolCallData(
            id="call_abc123",
            name="get_weather",
            arguments={"location": "Tokyo"},
            result="Sunny, 22°C",
        )
    ],
)

# System message
msg = ChatMessageData(
    role="system",
    content="Welcome! I'm your weather assistant.",
)
```

## ToolCallData

Represents a tool/function call.

```python
from castella.agent import ToolCallData

tool = ToolCallData(
    id: str,                      # Unique call ID
    name: str,                    # Tool/function name
    arguments: dict,              # Input arguments
    result: str = None,           # Output result
    is_error: bool = False,       # Error flag
    duration_ms: int = None,      # Execution time
)
```

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `id` | str | Unique identifier |
| `name` | str | Tool name |
| `arguments` | dict | Input parameters |
| `result` | str | Output or error |
| `is_error` | bool | Was error |
| `duration_ms` | int | Execution time |

### Example

```python
# Successful call
tool = ToolCallData(
    id="call_123",
    name="search_web",
    arguments={"query": "Python tutorials"},
    result="Found 10 results...",
    duration_ms=250,
)

# Failed call
tool = ToolCallData(
    id="call_456",
    name="send_email",
    arguments={"to": "invalid"},
    result="Invalid email address",
    is_error=True,
)
```

## Creating Messages from A2A

Convert A2A responses to ChatMessageData:

```python
from castella.agent import ChatMessageData
from castella.a2a import A2AClient

client = A2AClient("http://agent.example.com")
response = client.ask("Hello!")

msg = ChatMessageData(
    role="assistant",
    content=response,
)
```

## Creating Messages with Streaming

Build message incrementally:

```python
async def stream_message(client, query):
    chunks = []

    async for chunk in client.ask_stream(query):
        chunks.append(chunk)
        # Update message in place
        yield ChatMessageData(
            role="assistant",
            content="".join(chunks),
        )
```

## Message History Pattern

```python
from castella.core import ListState
from castella.agent import ChatMessageData

# Create message history
messages = ListState([
    ChatMessageData(role="system", content="Welcome!"),
])

# Add user message
messages.append(ChatMessageData(
    role="user",
    content="Hello!",
))

# Add assistant response
messages.append(ChatMessageData(
    role="assistant",
    content="Hi there! How can I help?",
))
```

## Serialization

Messages serialize to JSON:

```python
import json

msg = ChatMessageData(role="user", content="Hello")

# To dict
msg_dict = {
    "role": msg.role,
    "content": msg.content,
    "tool_calls": [
        {
            "id": tc.id,
            "name": tc.name,
            "arguments": tc.arguments,
            "result": tc.result,
        }
        for tc in (msg.tool_calls or [])
    ],
}

# To JSON
json_str = json.dumps(msg_dict)
```

## Rich Content

Messages support Markdown:

```python
msg = ChatMessageData(
    role="assistant",
    content="""
# Search Results

Found **3** relevant documents:

1. [Python Basics](https://example.com/1)
2. [Advanced Python](https://example.com/2)
3. [Python Best Practices](https://example.com/3)

```python
# Example code
print("Hello, World!")
```

| Feature | Status |
|---------|--------|
| Markdown | ✓ |
| Tables | ✓ |
| Code | ✓ |
""",
)
```

```

castella-agent-ui | SkillHub