Skip to content

HelpDesk Protocol Compatibility

DCAF Core provides full compatibility with the DuploCloud HelpDesk messaging protocol. This enables seamless integration with the HelpDesk frontend and existing agent infrastructure.


Overview

The HelpDesk protocol defines a rich message format that includes:

  • Commands: Terminal commands for user approval and execution
  • Tool Calls: Structured tool invocations with approval workflow
  • Platform Context: Runtime context (tenant, namespace, credentials)
  • Streaming Events: Real-time updates during agent execution

DCAF Core replicates this protocol in the dcaf.core module, ensuring compatibility while providing a cleaner, more Pythonic API.


Quick Start

from dcaf.core import (
    Agent,
    PlatformContext,
    ChatMessage,
    # HelpDesk DTOs
    DataDTO,
    CommandDTO,
    ExecutedCommandDTO,
    ToolCallDTO,
    ExecutedToolCallDTO,
    StreamEvent,
)
from dcaf.tools import tool

# Define tools
@tool(description="List Kubernetes pods")
def list_pods(namespace: str = "default") -> str:
    return kubectl(f"get pods -n {namespace}")

@tool(requires_approval=True, description="Delete a pod")
def delete_pod(name: str, namespace: str = "default") -> str:
    return kubectl(f"delete pod {name} -n {namespace}")

# Create agent
agent = Agent(tools=[list_pods, delete_pod])

# Create message with platform context
context = PlatformContext(
    tenant_name="acme-prod",
    k8s_namespace="default",
    duplo_base_url="https://acme.duplocloud.net",
)

response = agent.run(
    messages=[ChatMessage.user("Delete the nginx pod", context=context)]
)

Platform Context

PlatformContext carries runtime information about the user's environment:

from dcaf.core import PlatformContext

context = PlatformContext(
    # Kubernetes
    k8s_namespace="production",
    kubeconfig="/path/to/kubeconfig",

    # DuploCloud
    tenant_name="acme-prod",
    duplo_base_url="https://acme.duplocloud.net",
    duplo_token="eyJ...",

    # AWS
    aws_credentials={
        "access_key": "AKIA...",
        "secret_key": "...",
        "session_token": "...",
        "region": "us-west-2",
    },
)

Fields

Field Type Description
k8s_namespace str Kubernetes namespace for kubectl operations
kubeconfig str Path to kubeconfig or inline content
tenant_name str DuploCloud tenant name
duplo_base_url str DuploCloud API base URL
duplo_token str DuploCloud authentication token
aws_credentials dict AWS credentials (access_key, secret_key, etc.)

Using with Messages

from dcaf.core import ChatMessage, PlatformContext

# Pass context with a message
msg = ChatMessage.user(
    content="Delete the nginx pod",
    context=PlatformContext(tenant_name="acme", k8s_namespace="default"),
)

# Or use a dict
msg = ChatMessage.user(
    content="Delete the nginx pod",
    context={"tenant_name": "acme", "k8s_namespace": "default"},
)

Command DTOs

Commands represent terminal commands for user approval.

CommandDTO

from dcaf.core import CommandDTO

# Command awaiting approval
cmd = CommandDTO(
    command="kubectl delete pod nginx-1",
    execute=False,  # Not yet approved
)

# Approved command
approved_cmd = CommandDTO(
    command="kubectl delete pod nginx-1",
    execute=True,
)

# Rejected command
rejected_cmd = CommandDTO(
    command="rm -rf /",
    execute=False,
    rejection_reason="This command is too dangerous",
)

ExecutedCommandDTO

from dcaf.core import ExecutedCommandDTO

# Result of executed command
result = ExecutedCommandDTO(
    command="kubectl get pods",
    output="NAME         READY   STATUS\nnginx-1      1/1     Running",
)

With Files

Commands can include files to create before execution:

from dcaf.core.application.dto import CommandDTO, FileObject

cmd = CommandDTO(
    command="kubectl apply -f /tmp/deployment.yaml",
    files=[
        FileObject(
            file_path="/tmp/deployment.yaml",
            file_content="apiVersion: apps/v1\nkind: Deployment...",
        )
    ],
)

Tool Call DTOs

Tool calls follow the HelpDesk protocol format with all required fields.

ToolCallDTO

from dcaf.core import ToolCallDTO

tool_call = ToolCallDTO(
    id="call_abc123",
    name="delete_pod",
    input={"name": "nginx-1", "namespace": "default"},
    execute=False,  # Awaiting approval
    tool_description="Delete a Kubernetes pod",
    input_description={
        "name": {"type": "string", "description": "Pod name"},
        "namespace": {"type": "string", "description": "Namespace"},
    },
    intent="User wants to delete the failing nginx pod",
)

ExecutedToolCallDTO

from dcaf.core import ExecutedToolCallDTO

executed = ExecutedToolCallDTO(
    id="call_abc123",
    name="delete_pod",
    input={"name": "nginx-1", "namespace": "default"},
    output="pod \"nginx-1\" deleted",
)

Data Container

DataDTO is the container that holds all commands and tool calls:

from dcaf.core import DataDTO, CommandDTO, ToolCallDTO

data = DataDTO(
    cmds=[
        CommandDTO(command="kubectl get pods"),
    ],
    executed_cmds=[
        ExecutedCommandDTO(command="kubectl get nodes", output="..."),
    ],
    tool_calls=[
        ToolCallDTO(id="1", name="list_pods", input={}),
    ],
    executed_tool_calls=[
        ExecutedToolCallDTO(id="0", name="get_status", input={}, output="OK"),
    ],
)

# Check for pending items
if data.has_pending_items:
    print("User approval required")

Agent Response

AgentResponse includes the full HelpDesk data structure:

from dcaf.core import Agent
from dcaf.tools import tool

@tool(description="List pods")
def list_pods() -> str:
    return "nginx-1, nginx-2"

agent = Agent(tools=[list_pods])
response = agent.run(messages=[{"role": "user", "content": "List pods"}])

# Access the response
print(response.text)                    # Agent's text response
print(response.data.to_dict())          # Full HelpDesk data structure
print(response.commands)                # Pending commands
print(response.tool_calls)              # Pending tool calls
print(response.executed_commands)       # Commands that were run
print(response.executed_tool_calls)     # Tools that were run

# Convert to HelpDesk message format
helpdesk_msg = response.to_helpdesk_message()
# {'role': 'assistant', 'content': '...', 'data': {...}, 'meta_data': {...}}

Stream Events

DCAF Core supports all HelpDesk streaming event types:

from dcaf.core import StreamEvent, StreamEventType

# Create events programmatically
events = [
    # Text streaming
    StreamEvent.text_delta("Hello, "),
    StreamEvent.text_delta("world!"),

    # Commands for approval
    StreamEvent.commands_event([
        CommandDTO(command="kubectl get pods"),
    ]),

    # Tool calls for approval
    StreamEvent.tool_calls_event([
        ToolCallDTO(id="1", name="list_pods", input={}),
    ]),

    # Executed items
    StreamEvent.executed_commands_event([
        ExecutedCommandDTO(command="kubectl get nodes", output="..."),
    ]),
    StreamEvent.executed_tool_calls_event([
        ExecutedToolCallDTO(id="1", name="list_pods", input={}, output="..."),
    ]),

    # Completion
    StreamEvent.done(),
]

# Convert to HelpDesk NDJSON format
for event in events:
    print(event.to_dict())
    # {"type": "text_delta", "text": "Hello, "}
    # {"type": "commands", "commands": [...]}
    # etc.

Event Types

Event Type Description Data Fields
text_delta Streaming text chunk text
commands Commands for approval commands
tool_calls Tool calls for approval tool_calls
executed_commands Executed command results executed_cmds
executed_tool_calls Executed tool results executed_tool_calls
done Stream complete stop_reason (optional)
error Error occurred error, code

Server Integration

When using serve(), responses are automatically formatted for HelpDesk:

from dcaf.core import Agent, serve
from dcaf.tools import tool

@tool(description="List pods")
def list_pods(namespace: str = "default") -> str:
    return kubectl(f"get pods -n {namespace}")

agent = Agent(tools=[list_pods])
serve(agent)  # Responses are HelpDesk-compatible

The server endpoints return HelpDesk-formatted responses:

# Synchronous
curl -X POST http://localhost:8000/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{
      "role": "user",
      "content": "List pods",
      "platform_context": {
        "tenant_name": "acme",
        "k8s_namespace": "default"
      }
    }]
  }'

# Response
{
  "role": "assistant",
  "content": "Here are the pods...",
  "data": {
    "cmds": [],
    "executed_cmds": [],
    "tool_calls": [],
    "executed_tool_calls": [...]
  }
}

Mapping: Legacy vs Core

Legacy (dcaf/schemas) Core (dcaf/core) Notes
PlatformContext PlatformContext Same structure
Command CommandDTO Same structure
ExecutedCommand ExecutedCommandDTO Same structure
ToolCall ToolCallDTO Same structure
ExecutedToolCall ExecutedToolCallDTO Same structure
Data DataDTO Same structure
AgentMessage AgentResponse Core uses dataclass
TextDeltaEvent StreamEvent.text_delta() Factory method
CommandsEvent StreamEvent.commands_event() Factory method
DoneEvent StreamEvent.done() Factory method

Complete Example

from dcaf.core import (
    Agent, serve, PlatformContext, ChatMessage,
    CommandDTO, ToolCallDTO, DataDTO,
)
from dcaf.tools import tool

# Define tools
@tool(description="Get pod status")
def get_pods(namespace: str = "default") -> str:
    """List all pods in a namespace."""
    return subprocess.run(
        ["kubectl", "get", "pods", "-n", namespace],
        capture_output=True, text=True
    ).stdout

@tool(requires_approval=True, description="Delete a pod")
def delete_pod(name: str, namespace: str = "default") -> str:
    """Delete a pod. Requires user approval."""
    return subprocess.run(
        ["kubectl", "delete", "pod", name, "-n", namespace],
        capture_output=True, text=True
    ).stdout

# Create agent
agent = Agent(
    tools=[get_pods, delete_pod],
    system_prompt="You are a Kubernetes assistant.",
)

# Run with full HelpDesk context
context = PlatformContext(
    tenant_name="production",
    k8s_namespace="default",
    duplo_base_url="https://prod.duplocloud.net",
)

response = agent.run(
    messages=[
        ChatMessage.user("Delete the crashing pod", context=context)
    ]
)

# Check for pending approvals
if response.has_pending_approvals:
    print("The following actions need approval:")

    for cmd in response.pending_commands:
        print(f"  Command: {cmd.command}")

    for tc in response.pending_tool_calls:
        print(f"  Tool: {tc.name}({tc.input})")
        print(f"  Intent: {tc.intent}")

# Serve it
serve(agent)  # http://localhost:8000