Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,15 @@ PicoClaw can act as an L1 edge node in a distributed architecture, reporting to
```

When enabled, PicoClaw exposes:
- `GET /healthz` — Health check
- `GET /api/v1/status` — Node status
- `POST /api/v1/command` — Receive commands from fleet
- `GET /api/health` and `GET /healthz` — Health check
- `GET /api/status` and `GET /api/v1/status` — Node status
- `POST /api/command` and `POST /api/v1/command` — Receive commands from fleet, queued on MessageBus
- `POST /api/message` — Receive user/operator messages, queued on MessageBus

And periodically sends heartbeats (including gene stats) to the configured fleet manager.

An OpenAPI 3.0 specification is available at `pkg/edge/openapi.json`.

## Skills (6 built-in)

Skills are markdown files that teach the agent domain-specific knowledge.
Expand Down
2 changes: 1 addition & 1 deletion cmd/picoclaw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ func gatewayCmd() {
Token: cfg.Edge.CloudToken,
},
}
edgeServer = edge.NewServer(edgeCfg)
edgeServer = edge.NewServer(edgeCfg, msgBus)
edgeReporter = edge.NewReporter(edgeCfg)

// Wire gene engine into edge reporter for heartbeat stats
Expand Down
346 changes: 346 additions & 0 deletions pkg/edge/openapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
{
"openapi": "3.0.3",
"info": {
"title": "PicClaw Edge HTTP API",
"description": "Edge HTTP Server for PicClaw — enables PicoClaw to act as an L1 edge node in the Clawland cloud-edge architecture, communicating with L2 (NanoClaw) and L3 (MoltClaw).\n\nSee https://github.com/Clawland-AI/picclaw/issues/22 for the bounty scope.",
"version": "0.1.0",
"contact": {
"name": "Clawland AI",
"url": "https://clawland.ai"
}
},
"servers": [
{
"url": "http://localhost:9090",
"description": "Local edge node (default port)"
}
],
"paths": {
"/api/health": {
"get": {
"summary": "Health check",
"description": "Returns the health status of the edge node. Also available at `/healthz` for backward compatibility.",
"operationId": "healthCheck",
"tags": ["health", "status"],
"responses": {
"200": {
"description": "Node is healthy",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthResponse"
}
}
}
}
}
}
},
"/healthz": {
"get": {
"summary": "Health check (legacy)",
"description": "Legacy alias for GET /api/health.",
"operationId": "healthCheckLegacy",
"tags": ["health", "legacy"],
"responses": {
"200": {
"description": "Node is healthy",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthResponse"
}
}
}
}
}
}
},
"/api/status": {
"get": {
"summary": "Node status",
"description": "Returns detailed status of the edge node. Also available at `/api/v1/status` for backward compatibility.",
"operationId": "nodeStatus",
"tags": ["health", "status"],
"responses": {
"200": {
"description": "Node status information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatusResponse"
}
}
}
}
}
}
},
"/api/v1/status": {
"get": {
"summary": "Node status (legacy)",
"description": "Legacy alias for GET /api/status.",
"operationId": "nodeStatusLegacy",
"tags": ["health", "legacy"],
"responses": {
"200": {
"description": "Node status information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatusResponse"
}
}
}
}
}
}
},
"/api/command": {
"post": {
"summary": "Receive a command",
"description": "Accepts a command from the Fleet Manager and publishes it to the internal MessageBus for processing. Also available at `/api/v1/command` for backward compatibility.",
"operationId": "receiveCommand",
"tags": ["commands", "messages"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CommandRequest"
}
}
}
},
"responses": {
"202": {
"description": "Command accepted and queued",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CommandAccepted"
}
}
}
},
"400": {
"description": "Invalid request (malformed JSON or missing type)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/command": {
"post": {
"summary": "Receive a command (legacy)",
"description": "Legacy alias for POST /api/command.",
"operationId": "receiveCommandLegacy",
"tags": ["commands", "legacy"],
"requestBody": {
"$ref": "#/components/requestBodies/CommandRequest"
},
"responses": {
"202": {
"$ref": "#/components/responses/CommandAccepted"
},
"400": {
"$ref": "#/components/responses/Error"
},
"429": {
"$ref": "#/components/responses/RateLimited"
}
}
}
},
"/api/message": {
"post": {
"summary": "Receive a message",
"description": "Accepts a user/operator message and publishes it to the internal MessageBus for processing.",
"operationId": "receiveMessage",
"tags": ["messages"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageRequest"
}
}
}
},
"responses": {
"202": {
"description": "Message accepted and queued",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MessageAccepted"
}
}
}
},
"400": {
"description": "Invalid request (malformed JSON or missing content)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"HealthResponse": {
"type": "object",
"properties": {
"status": { "type": "string", "example": "ok" },
"agent": { "type": "string", "example": "picclaw" },
"node_id": { "type": "string", "example": "edge-01" },
"node_name": { "type": "string", "example": "picclaw-edge" },
"uptime": { "type": "string", "example": "5m30s" },
"version": { "type": "string", "example": "0.1.0" },
"bus_connected": { "type": "string", "enum": ["connected", "disconnected"] }
}
},
"StatusResponse": {
"type": "object",
"properties": {
"node_id": { "type": "string", "example": "edge-01" },
"node_name": { "type": "string", "example": "picclaw-edge" },
"status": { "type": "string", "example": "running" },
"uptime": { "type": "string", "example": "5m30s" },
"cloud": { "type": "string", "example": "http://localhost:8080" },
"bus_connected": { "type": "string", "enum": ["connected", "disconnected"] },
"endpoints": {
"type": "array",
"items": { "type": "string" }
}
}
},
"CommandRequest": {
"type": "object",
"required": ["type"],
"properties": {
"command_id": { "type": "string", "description": "Unique command identifier. Auto-generated if omitted." },
"type": { "type": "string", "description": "Command type (e.g., restart_sensor, ping)." },
"payload": { "type": "object", "description": "Command-specific payload." },
"sender_id": { "type": "string", "description": "Sender identifier. Defaults to 'fleet'." },
"session_key": { "type": "string", "description": "Session key. Auto-generated if omitted." },
"metadata": { "type": "object", "additionalProperties": { "type": "string" } }
}
},
"CommandAccepted": {
"type": "object",
"properties": {
"status": { "type": "string", "example": "accepted" },
"command_id": { "type": "string" },
"command": { "type": "string" },
"queued": { "type": "boolean" }
}
},
"MessageRequest": {
"type": "object",
"required": ["content"],
"properties": {
"message_id": { "type": "string", "description": "Unique message identifier. Auto-generated if omitted." },
"channel": { "type": "string", "description": "Message channel. Defaults to 'edge-api'." },
"sender_id": { "type": "string", "description": "Sender identifier. Defaults to 'fleet'." },
"chat_id": { "type": "string", "description": "Chat/conversation identifier." },
"content": { "type": "string", "description": "Message content (required)." },
"media": { "type": "array", "items": { "type": "string" }, "description": "Media attachment URLs." },
"session_key": { "type": "string", "description": "Session key. Auto-generated if omitted." },
"metadata": { "type": "object", "additionalProperties": { "type": "string" } }
}
},
"MessageAccepted": {
"type": "object",
"properties": {
"status": { "type": "string", "example": "accepted" },
"message_id": { "type": "string" },
"queued": { "type": "boolean" }
}
},
"ErrorResponse": {
"type": "object",
"properties": {
"error": { "type": "string" },
"code": { "type": "integer" }
}
}
},
"requestBodies": {
"CommandRequest": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CommandRequest"
}
}
}
}
},
"responses": {
"CommandAccepted": {
"description": "Accepted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CommandAccepted"
}
}
}
},
"Error": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"RateLimited": {
"description": "Too Many Requests",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
Loading