Ploinky Architecture

Technical architecture and implementation details of the Ploinky AI agent deployment system.

System Overview

Ploinky is built as a modular system with clear separation of concerns:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     User Interface                       β”‚
β”‚  (CLI Commands / Web Console / Chat / Dashboard)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Ploinky CLI Core                      β”‚
β”‚  (Command Handler / Service Manager / Config)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                   β”‚                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Routing Serverβ”‚   β”‚  Web Services β”‚   β”‚Container Mgmt β”‚
β”‚   (HTTP API)  β”‚   β”‚  (WebTTY/Chat)β”‚   β”‚ (Docker/Podman)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                   β”‚                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Agent Containers                      β”‚
β”‚         (Isolated Linux containers per agent)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Design Principles

  • Isolation First: Every agent runs in its own container
  • Workspace Scoped: All configuration is local to the project directory
  • Zero Global State: No system-wide installation or configuration
  • Git-Friendly: Configuration stored in .ploinky folder, can be gitignored
  • Runtime Agnostic: Supports Docker and Podman transparently

Core Components

CLI Command System (cli/commands/cli.js)

The main entry point that handles all user commands:

// Command routing structure
handleCommand(args) {
    switch(command) {
        case 'add':      // Repository management
        case 'enable':   // Agent/repo activation
        case 'start':    // Workspace initialization
        case 'shell':    // Interactive container access
        case 'webchat':  // Web interface launchers
        // ... more commands
    }
}

Service Layer (cli/services/)

Service Responsibility
workspace.js Manages .ploinky directory and configuration
docker/ Container lifecycle management modules (runtime helpers, interactive commands, agent management)
repos.js Repository management and agent discovery
agents.js Agent registration and configuration
secretVars.js Environment variable and secrets management
config.js Global configuration constants
help.js Help system and documentation

Container Management

Container Lifecycle

Ploinky manages containers with specific naming conventions and lifecycle hooks:

// Container naming convention
function getAgentContainerName(agentName, repoName) {
    const proj = path.basename(process.cwd()).replace(/[^a-zA-Z0-9_.-]/g, '_');
    const wsid = crypto.createHash('sha256')
        .update(process.cwd())
        .digest('hex')
        .substring(0, 6);
    return `ploinky_${proj}_${wsid}_agent_${agentName}`;
}

// Service container (for API endpoints)
function getServiceContainerName(agentName) {
    // Similar but with _service_ prefix
    return `ploinky_${proj}_${wsid}_service_${agentName}`;
}

Volume Mounts

Each container gets specific volume mounts for security:

{
    binds: [
        { source: process.cwd(), target: process.cwd() },      // Workspace
        { source: '/Agent', target: '/Agent' },                // Agent runtime
        { source: agentPath, target: '/code' }                 // Agent code
    ]
}

Runtime Detection

Automatically detects and uses available container runtime:

function getRuntime() {
    try {
        execSync('docker --version', { stdio: 'ignore' });
        return 'docker';
    } catch {
        try {
            execSync('podman --version', { stdio: 'ignore' });
            return 'podman';
        } catch {
            throw new Error('No container runtime found');
        }
    }
}

Routing Server

Purpose

The RoutingServer (cli/server/RoutingServer.js) acts as a reverse proxy, routing API requests to appropriate agent containers:

// routing.json structure
{
    "port": 8088,
    "static": {
        "agent": "demo",
        "container": "ploinky_myproject_abc123_service_demo",
        "hostPath": "/path/to/demo/agent"
    },
    "routes": {
        "agent1": {
            "container": "ploinky_myproject_abc123_service_agent1",
            "hostPort": 7001
        },
        "agent2": {
            "container": "ploinky_myproject_abc123_service_agent2",
            "hostPort": 7002
        }
    }
}

Request Flow

  1. Client sends request to http://localhost:8088/apis/agent1/method
  2. RoutingServer extracts agent name from path
  3. Looks up agent's container port in routing.json
  4. Proxies request to http://localhost:7001/api/method
  5. Returns response to client

Static File Serving

The router serves static files from the host filesystem in two ways:

  • Static agent root (existing): requests like /index.html map to static.hostPath in routing.json.
  • Agent-specific static routing (new): requests like /demo/ui/index.html map to the hostPath of the demo agent from routes.demo.hostPath.
// Static agent root
GET /index.html            β†’ routing.static.hostPath/index.html
GET /assets/app.js         β†’ routing.static.hostPath/assets/app.js

// Agent-specific static routing
GET /demo/ui/index.html    β†’ routing.routes.demo.hostPath/ui/index.html
GET /simulator/app.js      β†’ routing.routes.simulator.hostPath/app.js

Blob Storage API

The router exposes a simple blob storage API for large files with streaming upload/download.

// Upload (streaming)
POST /blobs
Headers:
  Content-Type: application/octet-stream
  X-Mime-Type: text/plain   # optional; falls back to Content-Type
Body: raw bytes (streamed)

Response: 201 Created
{ "id": "", "url": "/blobs/", "size": N, "mime": "text/plain" }

// Download (streaming, supports Range)
GET /blobs/<id>
HEAD /blobs/<id>
 - Streams bytes from .ploinky/blobs/<id> with metadata from .ploinky/blobs/<id>.json
 - Sets Content-Type, Content-Length, Accept-Ranges, and supports partial responses (206)

Workspace System

Directory Structure

.ploinky/
β”œβ”€β”€ agents/              # Agent registry (JSON)
β”œβ”€β”€ repos/               # Cloned agent repositories
β”‚   β”œβ”€β”€ basic/
β”‚   β”‚   β”œβ”€β”€ shell/
β”‚   β”‚   β”‚   └── manifest.json
β”‚   β”‚   └── node-dev/
β”‚   β”‚       └── manifest.json
β”‚   β”œβ”€β”€ demo/
β”‚   β”‚   β”œβ”€β”€ demo/
β”‚   β”‚   └── simulator/
β”‚   └── custom-repo/
β”œβ”€β”€ routing.json         # Router configuration
β”œβ”€β”€ .secrets            # Environment variables
└── running/            # Process PID files
    β”œβ”€β”€ router.pid
    β”œβ”€β”€ webtty.pid
    β”œβ”€β”€ webchat.pid
    └── dashboard.pid
logs/                   # Application logs
└── router.log

Agent Registry (agents/)

JSON file storing enabled agents and their configuration:

{
    "ploinky_project_abc123_agent_demo": {
        "agentName": "demo",
        "repoName": "demo",
        "containerImage": "node:18-alpine",
        "createdAt": "2024-01-01T00:00:00Z",
        "projectPath": "/home/user/project",
        "type": "agent",
        "config": {
            "binds": [...],
            "env": [...],
            "ports": [{"containerPort": 7000}]
        }
    }
}

Configuration Management

Workspace configuration persists across sessions:

// Stored in agents/_config
{
    "static": {
        "agent": "demo",
        "port": 8088
    }
}

Security Model

Container Isolation

  • Filesystem: Containers only access current workspace directory
  • Network: Isolated network namespace per container
  • Process: No access to host processes
  • Resources: Can set CPU/memory limits

Secret Management

Environment variables stored in .ploinky/.secrets with aliasing support:

API_KEY=sk-123456789
PROD_KEY=$API_KEY        # Alias reference
DATABASE_URL=postgres://localhost/db

Web Access Control

  • Password protection for web interfaces
  • Session-based authentication
  • WebSocket token validation
  • CORS headers configuration

Web Services Architecture

WebTTY/Console (cli/webtty/)

Provides terminal access through web browser:

// Component structure
server.js       // HTTP/WebSocket server
tty.js          // PTY management
console.js      // Client-side terminal UI
clientloader.js // Dynamic UI loader

WebChat (cli/webtty/chat.js)

Chat interface for CLI programs:

  • Captures stdout/stdin through PTY
  • WebSocket-based real-time communication
  • WhatsApp-style UI with message bubbles
  • Automatic reconnection handling

Dashboard (dashboard/)

Management interface components:

landingPage.js      // Main dashboard UI
auth.js             // Authentication
repositories.js     // Repo management
configurations.js   // Settings management
observability.js    // Monitoring views

WebSocket Protocol

// Message types
{ type: 'input', data: 'user command' }     // User input
{ type: 'output', data: 'program output' }  // Program output
{ type: 'resize', cols: 80, rows: 24 }      // Terminal resize
{ type: 'ping' }                             // Keep-alive

Data Flow Examples

Starting an Agent

1. User: enable agent demo
   β†’ Find manifest in repos/demo/demo/manifest.json
   β†’ Register in .ploinky/agents
   β†’ Generate container name

2. User: start demo 8088
   β†’ Read agents registry
   β†’ Start container for each agent
   β†’ Map ports (container:7000 β†’ host:7001)
   β†’ Update routing.json
   β†’ Start RoutingServer on 8088

3. Container startup:
   β†’ Pull image if needed
   β†’ Mount volumes (workspace, code, Agent)
   β†’ Set environment variables
   β†’ Run agent command or supervisor

API Request Routing

1. Client: GET http://localhost:8088/apis/simulator/monty-hall
   
2. RoutingServer:
   β†’ Extract agent: "simulator"
   β†’ Lookup in routing.json: hostPort: 7002
   β†’ Proxy to: http://localhost:7002/api/monty-hall
   
3. Agent Container:
   β†’ Process request
   β†’ Return response
   
4. RoutingServer:
   β†’ Forward response to client

WebChat Session

1. User: webchat secret python bot.py
   
2. WebTTY Server:
   β†’ Start PTY with command: python bot.py
   β†’ Create HTTP server on port 8080
   β†’ Serve chat.html interface
   
3. Browser connects:
   β†’ WebSocket handshake
   β†’ Authenticate with password
   β†’ Establish bidirectional channel
   
4. Message flow:
   β†’ User types in chat
   β†’ WebSocket β†’ Server β†’ PTY stdin
   β†’ Program output β†’ PTY stdout β†’ WebSocket β†’ Browser
   β†’ Display as chat bubble

Agent MCP Bridge

AgentServer (Agent/server/AgentServer.mjs) expune capabilități prin Model Context Protocol (MCP) folosind transport Streamable HTTP la ruta /mcp pe portul containerului (implicit 7000).

Router ↔ Agent Communication

  • RouterServer abstraction: RouterServer talks to agents through cli/server/AgentClient.js, which wraps MCP transports.
  • MCP protocol: AgentClient builds a StreamableHTTPClientTransport towards http://127.0.0.1:<hostPort>/mcp and exposes listTools(), callTool(), listResources(), and readResource().
  • Unified routing: Requests hitting /mcp carry commands such as list_tools, list_resources, or tool. RouterServer fans these calls out to every registered MCP endpoint and aggregates the replies.
  • Per-agent routes: Legacy paths like /mcps/<agent> remain available for direct calls when needed.
  • Transport independence: RouterServer stays agnostic of protocol details; AgentClient encapsulates the MCP implementation.

Tools and Resources

Agents declare their MCP surface through a JSON file committed alongside the agent source code: .ploinky/repos/<repo>/<agent>/mcp-config.json. When the CLI boots an agent container it copies this file to /tmp/ploinky/mcp-config.json (also keeping /code/mcp-config.json for reference). The file can expose tools, resources, and prompts, and each tool is executed by spawning a shell command. AgentServer does not register anything if the configuration file is missing.

{
  "tools": [
    {
      "name": "list_things",
      "title": "List Things",
      "description": "Enumerate items in a category",
      "command": "node scripts/list-things.js",
      "input": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "description": "fruits | animals | colors"
          }
        },
        "required": ["category"],
        "additionalProperties": false
      }
    }
  ],
  "resources": [
    {
      "name": "health",
      "uri": "health://status",
      "description": "Service health state",
      "mimeType": "application/json",
      "command": "node scripts/health.js"
    }
  ],
  "prompts": [
    {
      "name": "summarize",
      "description": "Short summary",
      "messages": [
        { "role": "system", "content": "You are a concise analyst." },
        { "role": "user", "content": "${input}" }
      ]
    }
  ]
}

AgentServer pipes a JSON payload to each command via stdin. Tool invocations receive { tool, input, metadata }; resources receive { resource, uri, params }. Command stdout is forwarded to the MCP response, while non-zero exit codes surface as MCP errors.

Performance Considerations

Container Optimization

  • Reuse existing containers when possible
  • Lazy image pulling
  • Shared base layers between agents
  • Volume mount caching

Network Efficiency

  • Local port mapping avoids network overhead
  • HTTP keep-alive for persistent connections
  • WebSocket for real-time communication
  • Request buffering and batching

Resource Management

  • Automatic container cleanup on exit
  • PID file tracking for process management
  • Log rotation for long-running services
  • Memory-efficient streaming for large outputs