Agent Specification

Complete guide to creating, configuring, and deploying Ploinky agents.

Manifest Structure

Every agent is defined by a manifest.json file that specifies its container, dependencies, and behavior:

Complete Manifest Schema

{
  // Required fields
  "container": "node:18-alpine",      // Docker/Podman image
  
  // Lifecycle commands
  "install": "npm install",           // Run once when agent is first created
  "update": "npm update",             // Run when agent needs updating
  
  // Execution modes
  "cli": "node repl.js",            // Interactive CLI command (cli)
  "agent": "node server.js",          // Long-running service (start)
  
  // Optional WebChat auto-configuration (runs on host before starting this agent)
  "webchat": "ploinky webchat MyAgent",
  
  // Metadata
  "about": "Express API server",      // Description shown in listings
  
  // Environment configuration
  "env": ["API_KEY", "DATABASE_URL"], // Required environment variables
  
  // Auto-configuration (optional)
  "enable": ["other-agent"],          // Auto-enable other agents
  "repos": {                          // Auto-add repositories
    "repo1": "https://github.com/org/repo.git"
  }
}

Field Descriptions

Field Required Description
container Yes Base container image from Docker Hub or other registry
install No One-time setup command for dependencies
update No Command to update agent dependencies
cli No Interactive command for ploinky cli (executed inside the container via the WebChat wrapper)
agent No Service command for ploinky start
about No Human-readable description
env No List of required environment variables
enable No Agents to auto-enable when this agent is enabled
repos No Repositories to auto-add when this agent is enabled
webchat No Bash command executed on the host before the agent container is started. Typical usage: auto-configure WebChat for this agent (e.g., ploinky webchat MyAgent or ploinky webchat ./scripts/menu.sh).

Agent Lifecycle

1. Creation

# Create new agent
new agent myrepo MyAgent node:20

# Creates:
.ploinky/repos/myrepo/MyAgent/
├── manifest.json
└── (agent files)

2. Installation

When an agent is first enabled, Ploinky runs the install command:

# manifest.json
"install": "npm install express body-parser"

# Executed in container:
docker run -v $PWD:$PWD node:18-alpine sh -c "npm install express body-parser"

3. Enablement

# Register agent in workspace
enable agent MyAgent

# Creates entry in .ploinky/agents
{
  "ploinky_project_abc123_agent_MyAgent": {
    "agentName": "MyAgent",
    "containerImage": "node:18-alpine",
    "createdAt": "2024-01-01T00:00:00Z",
    ...
  }
}

4. Startup

# Start all enabled agents
start
If the manifest defines webchat, Ploinky executes that command on the host before starting the agent container. This lets you automatically bind WebChat to the agent's CLI or to a local script/program.

5. Runtime

During runtime, agents can be in different states:

  • Running: Container active, service responding
  • Stopped: Container exists but not running
  • Exited: Container terminated (check exit code)
  • Removed: Container deleted

Command Types

CLI Command

Interactive command for direct user interaction:

# Usage
cli MyAgent

Agent Command

Long-running service for API endpoints:

# manifest.json
"agent": "node server.js"

# server.js
const express = require('express');
app.get('/mcp/status', (req, res) => {
    res.json({ status: 'running' });
});

app.listen(7000);

Supervisor Mode

If no agent command is specified, Ploinky uses the default supervisor:

# /Agent/AgentServer.mjs provides:
- HTTP server on port 7000
- Health check at /mcp/status
- Process management
- Automatic restarts

Environment Setup

Container Environment

Agents run with these environment variables:

AGENT_NAME=MyAgent           # Agent name
AGENT_REPO=myrepo           # Repository name
WORKSPACE_PATH=/workspace   # Mounted workspace
CODE_PATH=/code             # Agent code directory
PORT=7000                   # Default service port

Volume Mounts

Host Path Container Path Purpose
$(pwd) $(pwd) Workspace access
/Agent /Agent Supervisor runtime
.ploinky/repos/X/Y /code Agent code

Exposing Variables

# Set variable in workspace
ploinky var DATABASE_URL postgres://localhost/mydb

# Expose to agent
ploinky expose DATABASE_URL $DATABASE_URL MyAgent

# Agent can now access:
process.env.DATABASE_URL

API Development

Basic HTTP Server

// server.js
const http = require('http');

if (req.url === '/mcp/status') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ status: 'ok' }));
    } else if (req.url.startsWith('/mcp/')) {
        // Handle API routes
        const path = req.url.substring(5);
        res.writeHead(200);
        res.end(`API path: ${path}`);
    } else {
        res.writeHead(404);
        res.end('Not found');
    }
});

server.listen(7000, () => {
    console.log('Agent server running on port 7000');
});

Express.js API

// api.js
const express = require('express');
const app = express();

app.use(express.json());

app.get('/mcp/status', (req, res) => {
    res.json({ 
        status: 'healthy',
        agent: process.env.AGENT_NAME,
        uptime: process.uptime()
    });
});

// Custom endpoints
app.post('/mcp/process', (req, res) => {
    const { data } = req.body;
    // Process data
    res.json({ 
        result: `Processed: ${data}`,
        timestamp: new Date()
    });
});

app.listen(7000);

Python Flask API

# api.py
from flask import Flask, jsonify, request
import os

app = Flask(__name__)

@app.route('/mcp/status')
def status():
    return jsonify({
        'status': 'healthy',
        'agent': os.environ.get('AGENT_NAME'),
        'language': 'python'
    })

@app.route('/mcp/process', methods=['POST'])
def process():
    data = request.json
    return jsonify({
        'result': f"Processed: {data}",
        'method': 'python'
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=7000)

Accessing Your API

Once deployed, access your agent's API through the routing server:

# Local development
http://localhost:8088/mcps/MyAgent/status
http://localhost:8088/mcps/MyAgent/process

# From client
client status MyAgent
client task MyAgent

Example Agents

Simple Shell Agent

{
  "container": "alpine:latest",
  "install": "apk add curl jq",
  "cli": "/bin/sh",
  "about": "Alpine Linux shell with curl and jq"
}

Node.js Development Agent

{
  "container": "node:20",
  "install": "npm install -g nodemon typescript @types/node",
  "update": "npm update -g",
  "cli": "node",
  "agent": "nodemon --watch /workspace server.js",
  "about": "Node.js development environment with hot reload"
}

Python AI Assistant

{
  "container": "python:3.11",
  "install": "pip install openai numpy pandas flask",
  "update": "pip install --upgrade openai",
  "cli": "python -i",
  "agent": "python api_server.py",
  "env": ["OPENAI_API_KEY"],
  "about": "Python AI assistant with OpenAI integration"
}

Database Client Agent

{
  "container": "postgres:15",
  "install": "echo 'PostgreSQL client ready'",
  "cli": "psql -U postgres",
  "env": ["POSTGRES_PASSWORD"],
  "about": "PostgreSQL client for database operations"
}

Multi-Agent System

{
  "container": "node:18-alpine",
  "install": "npm install",
  "agent": "node orchestrator.js",
  "about": "Orchestrator agent",
  "enable": ["worker1", "worker2", "database"],
  "repos": {
    "workers": "https://github.com/myorg/worker-agents.git"
  }
}

Best Practices

Container Selection

  • Use Alpine-based images for smaller size
  • Pin specific versions (node:18.19.0 vs node:18)
  • Consider multi-stage builds for complex agents
  • Minimize layers in install commands

Security

  • Never hardcode secrets in manifest.json
  • Use environment variables for sensitive data
  • Run processes as non-root user when possible
  • Validate all input in API endpoints

Performance

  • Keep install commands minimal
  • Cache dependencies in agent directory
  • Use health checks for monitoring
  • Implement graceful shutdown handlers

Development

  • Test locally with shell first
  • Use cli for interactive debugging
  • Check logs with container runtime directly
  • Version control your agent code separately

Troubleshooting

Common Issues

Problem Cause Solution
Container exits immediately No long-running process Add agent command or use supervisor
Port 7000 not accessible Service not binding correctly Bind to 0.0.0.0:7000, not localhost
Install command fails Missing dependencies in base image Use fuller base image or add apt/apk commands
Environment variables not set Not exposed to agent Use expose command
API returns 404 Routing misconfiguration Check path starts with /mcp/

Debugging Commands

# Check agent status
status

Health Checks

Implement health endpoints for monitoring:

// Health check endpoint
app.get('/mcp/status', (req, res) => {
    const health = {
        status: 'healthy',
        checks: {
            database: checkDatabase(),
            memory: process.memoryUsage(),
            uptime: process.uptime()
        }
    };
    
    const isHealthy = Object.values(health.checks)
        .every(check => check !== false);
    
    res.status(isHealthy ? 200 : 503).json(health);
});

Advanced Features

Auto-Configuration

Agents can automatically configure their environment:

# manifest.json
{
  "container": "node:18",
  "agent": "node server.js",
  "enable": ["database", "cache"],  // Enable required agents
  "repos": {                        // Add required repos
    "utils": "https://github.com/org/utils.git"
  }
}

# When this agent is enabled:
1. Adds 'utils' repository
2. Enables 'database' agent
3. Enables 'cache' agent

Custom Supervisor

Override the default supervisor with custom logic:

// custom-supervisor.js
const { spawn } = require('child_process');
const http = require('http');

// Start main process
const main = spawn('node', ['app.js']);

// Health check server
http.createServer((req, res) => {
    if (req.url === '/mcp/status') {
        res.writeHead(200);
        res.end(JSON.stringify({
            status: main.exitCode === null ? 'running' : 'stopped',
            pid: main.pid
        }));
    }
}).listen(7000);

// Restart on crash
main.on('exit', (code) => {
    if (code !== 0) {
        console.log('Restarting after crash...');
        // Restart logic
    }
});