ActonOS API Development Skill
Use this skill when creating or modifying REST API endpoints in the internal/server/ package.
1. Package Overview
internal/server/
├── router.go # Chi router setup, global & auth middlewares, route tree
├── api_auth.go # Setup, login, logout, password change, auth status
├── api_dashboard.go # Dashboard aggregate metrics & summaries
├── api_agent.go # Agent CRUD, start/stop, chat, soul, memory-md, memories (pin/importance), cron
├── api_tasks.go # Autonomous Task matrix CRUD, Heartbeat config & manual pulse triggers
├── api_conversations.go # Chat conversations and message history
├── api_plugins.go # WASM plugin upload, enable/disable, logs, configuration & vault secrets
├── api_vault.go # Hardware-bound vault secret management
├── api_integrations.go # Channel accounts, pairing codes, sender authorization
├── api_tools.go # MCP servers, skills, tool execution, hub marketplace
├── api_workspace.go # Workspace file browser, read/write/mkdir/upload
├── api_system.go # Metrics, LLM health & retune, token usage ledger history, keys, identity, HAL
├── api_setup.go # Legacy/standalone setup endpoints
├── layered_fs.go # Layered filesystem (/data/overrides/ → go:embed fallback)
├── static.go # Embedded static asset server
└── server_test.go # Comprehensive endpoint test suite
2. HTTP Framework & Conventions
Base URL & Versioning
- Current Base URL:
/api(all routes are prefixed with/api) - Do not use
/api/v1/prefix until v1.0.0 is officially released.
Router Engine
ActonOS uses Chi v5 with standard library net/http:
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
Standard Response Envelope
Success (s.respondJSON(w, http.StatusOK, data)):
{
"data": { ... }
}
Error (s.respondError(w, http.StatusBadRequest, "INVALID_REQUEST", "description")):
{
"error": {
"code": "INVALID_REQUEST",
"message": "Human-readable explanation of error"
}
}
Common Helper Methods in Server
// 1. Respond with JSON data wrapped in {"data": ...}
func (s *Server) respondJSON(w http.ResponseWriter, status int, data any)
// 2. Respond with standard error envelope wrapped in {"error": {"code": ..., "message": ...}}
func (s *Server) respondError(w http.ResponseWriter, status int, code, message string)
// 3. Decode request body with 1MB safety limit
func (s *Server) decodeJSON(r *http.Request, v any) error
3. Route Organization & Authentication
Public Routes
GET /api/healthGET /api/modelsGET /api/notifications/push/vapid-keyGET /api/auth/statusPOST /api/auth/setupPOST /api/auth/loginPOST /api/auth/logout
Protected Subsystems
All other routes are nested inside r.Group with r.Use(s.RequireAuthMiddleware). Requests must include Authorization: Bearer <token> in the HTTP headers when authentication is initialized.
4. Implementing a New API Endpoint
Step 1: Add Request & Response Structs
type UpdateSoulRequest struct {
SoulContent string `json:"soul_content"`
}
type SoulResponse struct {
AgentID string `json:"agent_id"`
Content string `json:"content"`
UpdatedAt string `json:"updated_at"`
}
Step 2: Implement Handler Method on *Server
func (s *Server) handleSaveSoul(w http.ResponseWriter, r *http.Request) {
agentID := chi.URLParam(r, "agentID")
if agentID == "" {
agentID = agent.DefaultSystemAgentID
}
var req UpdateSoulRequest
if err := s.decodeJSON(r, &req); err != nil {
s.respondError(w, http.StatusBadRequest, "INVALID_BODY", "failed to decode json body")
return
}
if err := s.profileMgr.SaveSoul(r.Context(), agentID, req.SoulContent); err != nil {
s.respondError(w, http.StatusInternalServerError, "SAVE_FAILED", err.Error())
return
}
s.respondJSON(w, http.StatusOK, SoulResponse{
AgentID: agentID,
Content: req.SoulContent,
UpdatedAt: time.Now().UTC().Format(time.RFC3339),
})
}
Step 3: Register Route in internal/server/router.go
r.Route("/agents", func(r chi.Router) {
// ...
r.Route("/{agentID}", func(r chi.Router) {
// ...
r.Put("/soul", s.handleSaveSoul)
})
})
Step 4: Update Documentation and TypeScript Types
- Add endpoint entry to
docs/API.md - Add route and handler to
.agents/rules/source-registry.md - Add client method to
web/src/lib/api.tsand interfaces toweb/src/lib/types.ts
5. Streaming Endpoints (SSE / Server-Sent Events)
For streaming LLM tokens, reasoning thoughts, and tool call progress:
func (s *Server) handleChatStream(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
s.respondError(w, http.StatusInternalServerError, "STREAMING_UNSUPPORTED", "streaming not supported")
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
eventChan := make(chan agent.AgentStreamEvent, 64)
go func() {
_, _ = s.engine.ExecuteStepStreamWithHistory(
r.Context(), agentID, msg, history, eventChan,
)
}()
for ev := range eventChan {
data, _ := json.Marshal(ev)
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", ev.Type, data)
flusher.Flush()
}
}
The stream endpoint must flush live thought, token, tool_call,
tool_result, audit, done, and error events. It must not proxy to the
non-streaming JSON handler. Conversation messages are persisted before and
after the stream.
Realtime Operations WebSocket
GET /api/realtimeis a protected, same-origin WebSocket.- Browser authentication uses the HttpOnly
actonos_tokencookie set by setup/login; never put bearer tokens in WebSocket query strings. - Snapshots include hardware/Docker metrics, durable runs, pending approvals and token summaries.
- The stream is observation-only and must not expose an interactive shell or mutation channel.
6. Verification Checklist for API Changes
- Route is registered inside
router.go - Authentication middleware requirement is verified
- Error responses use standard
s.respondErrorwith appropriate HTTP status - TypeScript types in
web/src/lib/types.tsandapi.tsare synced -
docs/API.mdand.agents/rules/source-registry.mdare updated -
go test ./internal/server/...passes
7. Approval and Run APIs
api_approvals.goowns durable exact-action approval decisions.api_runs.goexposes durable run summaries and ordered execution events.- Authentication is not tool authorization. Execution handlers MUST call
ToolRegistry.Executeand return HTTP 202 forApprovalRequiredError. - MCP connection is a High-risk administrative action and requires approval.
- Preserve trace IDs through handler, engine, registry, approval, and audit calls.
- Workspace, skill, WASM, Hub, and restart mutations must use
requestAdminApprovaland exact dispatch fromapi_approvals.go. GET /api/system/audit/verifyis the canonical audit-chain integrity check.- Checkpointed approvals resume the same durable run; do not create a replacement.
- All server filesystem paths must derive from
Config.DataDir,Config.WorkspaceDir,Config.SkillsDir, orConfig.WASMDir; handlers must not assume the process working directory is the data root. - Provider credentials must use
Server.vault; never write API keys into JSON or.keyfiles. Preserve automatic legacy migration and fail closed without Vault. - SQLite backups must use
VACUUM INTOthrough the live database connection; never copy the main database file while WAL mode is active.