Data Model: Agent Local Server¶
Feature: 017-agent-local-server Date: 2025-12-29
Entity Relationship Diagram¶
┌─────────────────────────────────────────────────────────────────┐
│ AgentServer │
│ - agent_config: Agent │
│ - protocol: ProtocolType │
│ - port: int │
│ - sessions: SessionStore │
│ - is_ready: bool │
└─────────────────────────────────────────────────────────────────┘
│
│ manages
▼
┌─────────────────────────────────────────────────────────────────┐
│ SessionStore │
│ - sessions: dict[str, ServerSession] │
│ - ttl_seconds: int = 1800 (30 min) │
└─────────────────────────────────────────────────────────────────┘
│
│ contains
▼
┌─────────────────────────────────────────────────────────────────┐
│ ServerSession │
│ - session_id: str (ULID) │
│ - agent_executor: AgentExecutor │
│ - created_at: datetime │
│ - last_activity: datetime │
│ - message_count: int │
└─────────────────────────────────────────────────────────────────┘
│
│ uses
▼
┌─────────────────────────────────────────────────────────────────┐
│ AgentExecutor (existing) │
│ - agent_config: Agent │
│ - _thread_run: AgentThreadRun │
└─────────────────────────────────────────────────────────────────┘
Core Entities¶
1. AgentServer¶
The main server instance that hosts a single agent.
| Field | Type | Description | Constraints |
|---|---|---|---|
agent_config |
Agent |
Loaded agent configuration | Required, validated |
protocol |
ProtocolType |
Active protocol (ag-ui or rest) | Enum, default: ag-ui |
port |
int |
Server listening port | 1-65535, default: 8000 |
host |
str |
Server bind address | default: 0.0.0.0 |
sessions |
SessionStore |
Session storage | Lazy initialized |
is_ready |
bool |
Server ready state | Set after agent init |
cors_origins |
list[str] |
CORS allowed origins | default: ["*"] |
debug |
bool |
Debug logging enabled | default: False |
State Transitions:
INITIALIZING → READY → RUNNING → SHUTTING_DOWN → STOPPED
2. SessionStore¶
In-memory session storage with automatic TTL cleanup.
| Field | Type | Description | Constraints |
|---|---|---|---|
sessions |
dict[str, ServerSession] |
Active sessions | Keyed by session_id |
ttl_seconds |
int |
Session timeout | default: 1800 (30 min) |
_cleanup_task |
asyncio.Task |
Background cleanup | Started on init |
Operations:
- get(session_id) -> ServerSession | None
- create(agent_executor) -> ServerSession
- delete(session_id) -> bool
- touch(session_id) -> None (updates last_activity)
- cleanup_expired() -> int (returns count removed)
3. ServerSession¶
Individual conversation session with an agent.
| Field | Type | Description | Constraints |
|---|---|---|---|
session_id |
str |
Unique identifier | ULID format |
agent_executor |
AgentExecutor |
Agent execution context | Required |
created_at |
datetime |
Session creation time | UTC, immutable |
last_activity |
datetime |
Last request time | UTC, updated on activity |
message_count |
int |
Messages in session | Incremented per message |
Lifecycle:
Created (new request without session_id)
↓
Active (processing requests)
↓
Expired (30 min inactivity) → Cleaned up
or
Deleted (explicit DELETE request) → Cleaned up
Request/Response Models¶
4. ChatRequest¶
Request payload for chat endpoints.
| Field | Type | Description | Constraints |
|---|---|---|---|
message |
str |
User message content | Required, max 10,000 chars |
session_id |
str | None |
Session identifier | Optional, ULID if provided |
files |
list[FileContent] | None |
Attached files | Optional, max 10 files |
Validation Rules:
- message must not be empty or whitespace-only
- session_id if provided must be valid ULID format
- Total file size must not exceed 100 MB
- Individual file size must not exceed 50 MB
4a. FileContent¶
Binary file content for multimodal inputs.
| Field | Type | Description | Constraints |
|---|---|---|---|
content |
str |
Base64-encoded file data | Required for JSON requests |
mime_type |
str |
MIME type of the file | Required, validated |
filename |
str | None |
Original filename | Optional |
Supported MIME Types:
- Images: image/png, image/jpeg, image/gif, image/webp
- Documents: application/pdf
- Office: application/vnd.openxmlformats-officedocument.wordprocessingml.document (docx), application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (xlsx), application/vnd.openxmlformats-officedocument.presentationml.presentation (pptx)
- Text: text/plain, text/csv, text/markdown
Processing: Files are processed by HoloDeck's FileProcessor before being sent to the agent:
- Images → OCR text extraction
- PDFs → Text extraction
- Office docs → Markdown conversion
5. ChatResponse¶
Response payload for synchronous chat endpoint (REST only).
| Field | Type | Description | Constraints |
|---|---|---|---|
message_id |
str |
Unique message identifier | ULID |
content |
str |
Agent response text | May be empty |
session_id |
str |
Session used/created | ULID |
tool_calls |
list[ToolCallInfo] |
Tools invoked | May be empty |
tokens_used |
TokenUsage | None |
Token consumption | Optional |
execution_time_ms |
int |
Request latency | Milliseconds |
6. ToolCallInfo¶
Tool execution information in response.
| Field | Type | Description | Constraints |
|---|---|---|---|
name |
str |
Tool name | Required |
arguments |
dict[str, Any] |
Tool parameters | JSON-serializable |
status |
str |
Execution status | "success" or "error" |
7. HealthResponse¶
Response for health check endpoints.
| Field | Type | Description | Constraints |
|---|---|---|---|
status |
str |
Overall status | "healthy" or "unhealthy" |
agent_name |
str | None |
Loaded agent name | Present if agent loaded |
agent_ready |
bool |
Agent ready state | True when initialized |
active_sessions |
int |
Session count | >= 0 |
uptime_seconds |
float |
Server uptime | >= 0 |
8. ProblemDetail¶
RFC 7807 error response format.
| Field | Type | Description | Constraints |
|---|---|---|---|
type |
str |
Error type URI | URL format |
title |
str |
Short description | Human-readable |
status |
int |
HTTP status code | 4xx or 5xx |
detail |
str | None |
Detailed message | Optional |
instance |
str | None |
Request identifier | Optional, for tracing |
Enumerations¶
ProtocolType¶
class ProtocolType(str, Enum):
AG_UI = "ag-ui"
REST = "rest"
ServerState¶
class ServerState(str, Enum):
INITIALIZING = "initializing"
READY = "ready"
RUNNING = "running"
SHUTTING_DOWN = "shutting_down"
STOPPED = "stopped"
Pydantic Model Definitions¶
from datetime import datetime
from enum import Enum
from typing import Any
from pydantic import BaseModel, Field, field_validator
from python_ulid import ULID
class ProtocolType(str, Enum):
AG_UI = "ag-ui"
REST = "rest"
SUPPORTED_MIME_TYPES = {
"image/png", "image/jpeg", "image/gif", "image/webp",
"application/pdf",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain", "text/csv", "text/markdown",
}
class FileContent(BaseModel):
"""Binary file content for multimodal inputs."""
content: str = Field(..., description="Base64-encoded file data")
mime_type: str = Field(..., description="MIME type of the file")
filename: str | None = Field(default=None, description="Original filename")
@field_validator("mime_type")
@classmethod
def validate_mime_type(cls, v: str) -> str:
if v not in SUPPORTED_MIME_TYPES:
raise ValueError(f"Unsupported MIME type: {v}")
return v
@field_validator("content")
@classmethod
def validate_base64(cls, v: str) -> str:
import base64
try:
# Validate it's valid base64
base64.b64decode(v, validate=True)
except Exception:
raise ValueError("content must be valid base64")
return v
class ChatRequest(BaseModel):
"""Request payload for chat endpoints."""
message: str = Field(..., min_length=1, max_length=10_000)
session_id: str | None = Field(default=None)
files: list[FileContent] | None = Field(default=None, max_length=10)
@field_validator("message")
@classmethod
def message_not_blank(cls, v: str) -> str:
if not v.strip():
raise ValueError("message cannot be blank")
return v
@field_validator("session_id")
@classmethod
def valid_ulid(cls, v: str | None) -> str | None:
if v is not None:
try:
ULID.from_str(v)
except ValueError:
raise ValueError("session_id must be valid ULID")
return v
class ToolCallInfo(BaseModel):
"""Tool execution information."""
name: str
arguments: dict[str, Any] = Field(default_factory=dict)
status: str = "success"
class ChatResponse(BaseModel):
"""Response payload for synchronous chat."""
message_id: str
content: str
session_id: str
tool_calls: list[ToolCallInfo] = Field(default_factory=list)
tokens_used: dict[str, int] | None = None
execution_time_ms: int
class HealthResponse(BaseModel):
"""Health check response."""
status: str
agent_name: str | None = None
agent_ready: bool = False
active_sessions: int = 0
uptime_seconds: float = 0.0
class ProblemDetail(BaseModel):
"""RFC 7807 Problem Details error response."""
type: str = "about:blank"
title: str
status: int
detail: str | None = None
instance: str | None = None
Relationship to Existing Models¶
| New Model | Existing Model | Relationship |
|---|---|---|
ServerSession |
ChatSession |
Extends with HTTP context |
ChatRequest |
N/A | New for HTTP layer |
FileContent |
TestCaseFile |
Similar structure for multimodal |
ChatResponse |
AgentResponse |
Wraps for HTTP response |
ToolCallInfo |
ToolExecution |
Simplified for API |
HealthResponse |
N/A | New for health checks |
Storage Considerations¶
| Entity | Storage | Persistence | Cleanup |
|---|---|---|---|
AgentServer |
Memory | Process lifetime | On shutdown |
SessionStore |
Memory | Process lifetime | On shutdown |
ServerSession |
Memory | 30-min TTL | Background task |
Note: Persistent storage for sessions is explicitly deferred per spec assumptions. Future work may add Redis or database backing.