A PHP 8.0+ client library for interacting with a PLATO room server. Submit tiles, query knowledge, manage rooms, and register fleet agents.
Part of the Cocapn fleet — lighthouse keeper architecture.
composer require superinstance/plato-client-phpOr manually:
git clone https://github.com/SuperInstance/plato-client-php.git
cd plato-client-php
composer installuse SuperInstance\PlatoClient\PlatoClient;
$client = new PlatoClient('http://localhost:8847');
// Check server status
$status = $client->getStatus();
echo "Server version: {$status['version']}\n";
// Submit a tile
$result = $client->submitTile(
'my_room',
'What is Phi?',
'Phi measures how much a room\'s whole exceeds the sum of its tiles.',
['confidence' => 0.85]
);
echo "Tile hash: {$result['tile_hash']}\n";
// Query tiles
$results = $client->queryTiles('my_room', 'Phi', limit: 10);
foreach ($results as $tile) {
echo "- {$tile['question']}\n";
}plato-client-php/
├── composer.json
├── README.md
└── src/
├── PlatoClient.php # Main HTTP client
├── PlatoException.php # Error handling
├── PlatoErrorCode.php # Error code enum
├── Tile.php # Tile value object
├── Room.php # Room value object
├── AgentRegistry.php # Fleet agent helpers
├── TileQuery.php # Query builder
└── CLI.php # Command-line tool
The tile is the fundamental unit of PLATO knowledge. Each tile is a structured Q&A pair:
question— the anchor/question textanswer— the learned contentconfidence— 0.0 to 1.0 quality signaldomain/room_id— which room it belongs to_hash— content-derived identifiersource/author— who/what submitted it
Rooms are named collections of tiles. They accumulate knowledge over time. Each room has:
id— room name (used as domain)description— room purposetile_count— number of tilescreated_at— when the room was createdagents— fleet agents active in this room
$client = new PlatoClient(string $serverUrl, ?string $apiToken = null);$serverUrl— base URL of PLATO server (e.g.,http://localhost:8847)$apiToken— optional API token. Falls back toPLATO_API_TOKENenv var.
$status = $client->getStatus(): array;Returns server status including version, room count, gate statistics.
$status = $client->getStatus();
echo $status['version']; // e.g., "v2.0"
echo $status['rooms']; // room count
echo $status['total_tiles']; // total tiles across all rooms$rooms = $client->listRooms(int $limit = 100): array;List all rooms. Returns array of room summaries.
$rooms = $client->listRooms(limit: 50);
foreach ($rooms as $name => $meta) {
echo "{$name}: {$meta['tile_count']} tiles\n";
}$room = $client->createRoom(string $roomId, string $description = ''): array;Create a new room.
$client->createRoom('fleet_orchestration', 'Fleet coordination knowledge');$room = $client->getRoom(string $roomId): array;Get room details including all tiles.
$room = $client->getRoom('oracle1_history');
echo count($room['tiles']) . " tiles in this room\n";$deleted = $client->deleteRoom(string $roomId): bool;Delete a room. Returns true on success, false if room not found.
$result = $client->submitTile(
string $roomId,
string $question,
string $answer,
array $metadata = []
): array;Submit a tile to a room. The server validates through deadband gates (length, no absolute claims).
$result = $client->submitTile(
'my_room',
'What is a deadband?',
'A deadband is a range of values where a system does not react to input changes.',
['confidence' => 0.8, 'source' => 'oracle1']
);
echo "Accepted: " . ($result['status'] === 'accepted') . "\n";$tile = $client->getTile(string $roomId, string $tileId): array;Get a specific tile by ID from a room.
$tiles = $client->queryTiles(string $roomId, string $query, int $limit = 20): array;Search tiles by query string. Filters by room if $roomId is non-empty.
$results = $client->queryTiles('fleet_orchestration', 'consciousness', limit: 10);$tiles = $client->getRecentTiles(string $roomId, int $limit = 20): array;Get most recent tiles from a room, sorted newest first.
$client->streamTiles(string $roomId, callable $callback): void;Stream tiles in real-time using SSE or long-polling. The callback receives each new tile.
$client->streamTiles('oracle1_history', function(array $tile) {
echo "New tile: {$tile['question']}\n";
});
// Runs indefinitely — use in a separate process or with timeout$result = $client->registerAgent(string $agentId, array $capabilities): array;Register a fleet agent with the server.
$result = $client->registerAgent('my-agent-php', ['search', 'submit', 'query']);$client->setTimeout(int $seconds): void;Set the request timeout. Default is 10 seconds.
use SuperInstance\PlatoClient\Tile;
$tile = Tile::fromArray($data);
echo $tile->id; // tile hash
echo $tile->question; // anchor text
echo $tile->answer; // content
echo $tile->confidence; // 0.0-1.0
$arr = $tile->toArray(); // convert to array
$valid = $tile->validate(); // check required fieldsuse SuperInstance\PlatoClient\Room;
$room = Room::fromArray($data);
echo $room->id; // room name
echo $room->tileCount; // number of tiles
echo $room->description; // room purposeUse TileQuery to construct complex searches:
use SuperInstance\PlatoClient\TileQuery;
$query = TileQuery::create('fleet_orchestration')
->whereConfidence(min: 0.7)
->whereAuthor('oracle1')
->whereQuestionContains('consciousness')
->orderBy('created_at', 'desc')
->limit(20);
$results = $query->execute($client);
// Or pass the query string directly:
$tiles = $client->queryTiles('fleet_orchestration', $query->build());Available filters:
whereConfidence(float $min)— minimum confidence scorewhereAuthor(string $author)— filter by submitterwhereUpdatedSince(DateTime $since)— tiles updated after this timewhereUpdatedUntil(DateTime $until)— tiles updated before this timewhereQuestionContains(string $text)— question contains textwhereAnswerContains(string $text)— answer contains textwhereMetadataKey(string $key, $value)— filter by metadata fieldwhereLinkedTo(string $tileId)— tiles linked to a specific tileorderBy(string $field, string $direction)— sort field and directionlimit(int $n)— result limit
Helper for fleet agent operations:
use SuperInstance\PlatoClient\AgentRegistry;
// Register an agent
AgentRegistry::registerAgent($client, 'my-agent', ['search', 'submit']);
// Send heartbeat
$ok = AgentRegistry::heartbeat($client, 'my-agent');
// Get agent status
$status = AgentRegistry::getAgentStatus($client, 'my-agent');
echo $status['status']; // 'active', 'unknown', etc.All errors throw PlatoException with a PlatoErrorCode:
use SuperInstance\PlatoClient\PlatoClient;
use SuperInstance\PlatoClient\PlatoException;
use SuperInstance\PlatoClient\PlatoErrorCode;
try {
$client->submitTile('room', 'Q', 'A');
} catch (PlatoException $e) {
match ($e->getErrorCode()) {
PlatoErrorCode::CONNECTION_ERROR => print "Cannot reach server\n",
PlatoErrorCode::ROOM_NOT_FOUND => print "Room doesn't exist\n",
PlatoErrorCode::AUTH_ERROR => print "Authentication failed\n",
PlatoErrorCode::TILE_NOT_FOUND => print "Tile not found\n",
PlatoErrorCode::QUERY_ERROR => print "Bad query syntax\n",
PlatoErrorCode::SERVER_ERROR => print "Server error: {$e->getMessage()}\n",
PlatoErrorCode::TIMEOUT => print "Request timed out\n",
};
}Static factories for common errors:
PlatoException::connectionError('http://localhost:8847');
PlatoException::connectionRefused('http://localhost:8847');
PlatoException::timeout('http://localhost:8847', 10);
PlatoException::authError();
PlatoException::roomNotFound('my_room');
PlatoException::tileNotFound('my_room', 'abc123');
PlatoException::queryError('invalid query syntax');
PlatoException::serverError('Internal error', 500);| Variable | Description | Default |
|---|---|---|
PLATO_API_TOKEN |
API token for authentication | none |
PLATO_SERVER_URL |
Base URL for CLI commands | http://localhost:8847 |
# Check server status
php src/CLI.php status
# List rooms
php src/CLI.php rooms --limit=50
# List tiles in a room
php src/CLI.php tiles fleet_orchestration --limit=20
# Submit a tile
php src/CLI.php submit my_room "What is Phi?" "Phi measures knowledge integration."
# Query tiles
php src/CLI.php query fleet_orchestration "consciousness" --limit=10
# Stream tiles in real-time
php src/CLI.php stream oracle1_history
# Agent management
php src/CLI.php agent register --agent-id=my-agent --capabilities=search,submit
php src/CLI.php agent heartbeat --agent-id=my-agent
php src/CLI.php agent status --agent-id=my-agent- Uses cURL if available, falls back to
file_get_contentswith stream context - 10-second default timeout, configurable via
setTimeout() - JSON-encoded request bodies
- Bearer token authentication when
apiTokenis set - Connection refused and timeout errors throw typed
PlatoException
$client = new PlatoClient('http://localhost:8847');
// Create a room if it doesn't exist
try {
$client->createRoom('knowledge_fleet', 'Fleet knowledge base');
} catch (PlatoException $e) {
// Room may already exist — that's fine
}
// Submit a tile
$tile = $client->submitTile(
'knowledge_fleet',
'How does agent registration work?',
'Agents register via POST /agents/{agent_id} with capabilities list. ' .
'They send periodic heartbeats to stay active in the fleet registry.',
['confidence' => 0.85, 'source' => 'oracle1']
);
// Query for it
$results = $client->queryTiles('knowledge_fleet', 'agent registration');
foreach ($results as $result) {
echo "{$result['question']}: {$result['answer']}\n";
}
// Use query builder for complex searches
$tiles = TileQuery::create('knowledge_fleet')
->whereConfidence(0.7)
->whereAuthor('oracle1')
->orderBy('created_at', 'desc')
->limit(10)
->execute($client);$client = new PlatoClient('http://localhost:8847');
$count = 0;
$client->streamTiles('oracle1_history', function(array $tile) use (&$count) {
$count++;
echo "[{$count}] {$tile['question']}\n";
echo " Answer: " . substr($tile['answer'], 0, 100) . "...\n";
});$client = new PlatoClient('http://localhost:8847');
$agentId = 'background-worker-php';
AgentRegistry::registerAgent($client, $agentId, ['search', 'submit']);
while (true) {
$ok = AgentRegistry::heartbeat($client, $agentId);
echo $ok ? "Heartbeat OK\n" : "Heartbeat failed\n";
sleep(30); // every 30 seconds
}🦐 Cocapn fleet — lighthouse keeper architecture