Documentation

Complete reference for Sinas — fetched live from GitHub.

Sinas Documentation

Introduction — What is Sinas?

Sinas is a platform for building AI-powered applications. It brings together multi-provider LLM agents, serverless Python functions, persistent state, database querying, file storage, and template rendering — all behind a single API with role-based access control.

What you can do with Sinas:

  • Build AI agents with configurable LLM providers (OpenAI, Anthropic, Mistral, Ollama), tool calling, streaming responses, and agent-to-agent orchestration.
  • Run Python functions in isolated Docker containers, triggered by agents, webhooks, cron schedules, or the API.
  • Store and retrieve state across conversations with namespace-based access control.
  • Query external databases (PostgreSQL, ClickHouse, Snowflake) through saved SQL templates that agents can use as tools.
  • Manage files with versioning, metadata validation, and upload processing hooks.
  • Render templates using Jinja2 for emails, notifications, and dynamic content.
  • Define everything in YAML for GitOps workflows with idempotent apply, change detection, and dry-run.

Sinas runs as a set of Docker services: the API server, queue workers (for functions and agents), a scheduler, PostgreSQL, PgBouncer, Redis, ClickHouse (optional for request logging), and a web console.


Deployment

Prerequisites

  • A VPS or server with 2+ CPU cores, 4GB+ RAM, 50GB+ storage
  • A domain name pointed at the server (A record)
  • An SMTP service for login emails (SendGrid, Mailgun, AWS SES, etc.)

Install

sudo curl -fsSL https://raw.githubusercontent.com/sinas-platform/sinas/main/install.sh -o /tmp/sinas-install.sh && sudo bash /tmp/sinas-install.sh

The installer will:

  • Install Docker if needed
  • Generate secure keys (SECRET_KEY, ENCRYPTION_KEY, DATABASE_PASSWORD)
  • Prompt for your domain, admin email, and SMTP credentials
  • Create .env in /opt/sinas/
  • Pull pre-built images from the container registry and start all services
  • Caddy automatically provisions SSL via Let's Encrypt

All services start automatically: PostgreSQL, PgBouncer, Redis, ClickHouse, the backend API (port 8000), queue workers, the scheduler, and the web console (port 51245). Migrations run automatically on startup.

Update

cd /opt/sinas
docker compose pull
docker compose up -d

Manual development installation

For local development, see INSTALL.md.

3. Log in

  1. Open the console at https://yourdomain.com:51245
  2. Enter your SUPERADMIN_EMAIL address
  3. Check your inbox for the 6-digit OTP code
  4. Enter the code to receive your access token

4. Configure an LLM provider

Before agents can work, you need at least one LLM provider:

curl -X POST https://yourdomain.com/api/v1/llm-providers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "openai",
    "provider_type": "openai",
    "api_key": "sk-...",
    "default_model": "gpt-4o",
    "is_default": true
  }'

5. Start chatting

A default agent is created on startup. Create a chat and send a message:

# Create a chat with the default agent
curl -X POST https://yourdomain.com/agents/default/default/chats \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

# Send a message (use the chat_id from the response)
curl -X POST https://yourdomain.com/chats/{chat_id}/messages/stream \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "Hello!"}'

Environment Variables

Required

Variable Description
SECRET_KEY JWT signing key (auto-generated by install script)
ENCRYPTION_KEY Fernet key for encrypting stored credentials (LLM API keys, DB passwords)
DATABASE_PASSWORD PostgreSQL password
SMTP_HOST SMTP server hostname (e.g., smtp.sendgrid.net)
SMTP_PORT SMTP port (typically 587)
SMTP_USER SMTP username
SMTP_PASSWORD SMTP password or API key
SMTP_DOMAIN Email "from" domain (e.g., example.com)
SUPERADMIN_EMAIL Admin user created on first startup
DOMAIN Your domain for Caddy auto-HTTPS (e.g., sinas.example.com)

Application

Variable Default Description
DEBUG false Enable verbose logging
CORS_ORIGINS (none) Comma-separated allowed origins
ACCESS_TOKEN_EXPIRE_MINUTES 15 JWT access token lifetime
REFRESH_TOKEN_EXPIRE_DAYS 30 Refresh token lifetime
OTP_EXPIRE_MINUTES 10 OTP code validity
OTP_MAX_ATTEMPTS 2 Wrong OTP guesses before invalidation

Rate Limiting

Variable Default Description
RATE_LIMIT_LOGIN_IP_MAX 10 Max login requests per IP per window
RATE_LIMIT_LOGIN_EMAIL_MAX 5 Max login requests per email per window
RATE_LIMIT_OTP_IP_MAX 10 Max OTP verify requests per IP per window
RATE_LIMIT_WINDOW_SECONDS 900 Rate limit window (15 minutes)

Function Execution

Variable Default Description
FUNCTION_TIMEOUT 300 Max execution time in seconds
MAX_FUNCTION_MEMORY 512 Memory limit in MB
MAX_FUNCTION_CPU 1.0 CPU cores per function
MAX_FUNCTION_STORAGE 1g Disk storage limit
FUNCTION_CONTAINER_IDLE_TIMEOUT 3600 Idle container cleanup (seconds)
ALLOW_PACKAGE_INSTALLATION true Allow pip install in functions
ALLOWED_PACKAGES (all) Comma-separated package whitelist

Sandbox Containers

Variable Default Description
SANDBOX_MIN_SIZE 4 Containers to pre-create
SANDBOX_MAX_SIZE 20 Maximum sandbox containers
SANDBOX_MIN_IDLE 2 Replenish when idle drops below this
SANDBOX_MAX_EXECUTIONS 100 Recycle after N executions

Agent Processing

Variable Default Description
MAX_TOOL_ITERATIONS 25 Max consecutive tool-call rounds per message
MAX_HISTORY_MESSAGES 100 Messages loaded for conversation context
AGENT_JOB_TIMEOUT 600 Agent job timeout (seconds)
CODE_EXECUTION_TIMEOUT 120 Code execution timeout (seconds)

Scaling

Variable Default Description
BACKEND_REPLICAS 1 Backend API replicas
UVICORN_WORKERS 4 Workers per backend replica
QUEUE_WORKER_REPLICAS 2 Function queue worker replicas
QUEUE_AGENT_REPLICAS 2 Agent queue worker replicas
QUEUE_FUNCTION_CONCURRENCY 10 Concurrent functions per worker
QUEUE_AGENT_CONCURRENCY 5 Concurrent agent jobs per worker

Resource Limits (Docker)

Variable Default Description
APP_CPU_LIMIT 2.0 Max CPU cores for backend container
APP_MEMORY_LIMIT 2G Max RAM for backend container
APP_CPU_RESERVATION 0.5 Guaranteed CPU cores
APP_MEMORY_RESERVATION 512M Guaranteed RAM

Database

Variable Default Description
DATABASE_USER postgres PostgreSQL user
DATABASE_HOST postgres PostgreSQL host
DATABASE_PORT 5432 PostgreSQL port
DATABASE_NAME sinas Database name
DATABASE_URL (built from above) Full connection string (overrides individual vars)
REDIS_URL redis://redis:6379/0 Redis connection string

ClickHouse (Optional)

Variable Default Description
CLICKHOUSE_HOST clickhouse ClickHouse host
CLICKHOUSE_PORT 8123 ClickHouse HTTP port
CLICKHOUSE_USER default ClickHouse user
CLICKHOUSE_PASSWORD (empty) ClickHouse password
CLICKHOUSE_DATABASE sinas ClickHouse database
CLICKHOUSE_RETENTION_DAYS 90 Data retention (no S3)
CLICKHOUSE_HOT_RETENTION_DAYS 30 Hot retention (with S3)
CLICKHOUSE_S3_ENDPOINT (none) S3 endpoint for tiered storage
CLICKHOUSE_S3_BUCKET (none) S3 bucket name
CLICKHOUSE_S3_ACCESS_KEY_ID (none) S3 access key
CLICKHOUSE_S3_SECRET_ACCESS_KEY (none) S3 secret key
CLICKHOUSE_S3_REGION us-east-1 S3 region

Declarative Config

Variable Default Description
CONFIG_FILE (none) Path to YAML config file
AUTO_APPLY_CONFIG false Apply config file on startup

The simplest useful setup requires the required variables plus a configured LLM provider. Everything else (functions, skills, state, etc.) is optional and can be added incrementally.


Concepts

Namespaces

Most resources are organized by namespace and name. A namespace groups related resources (e.g., support/ticket-agent, analytics/daily-report). The default namespace is default. Resources are uniquely identified by their namespace/name pair.

Tools

Agents interact with the outside world through tools — capabilities you enable per agent:

Tool type What it does
Functions Execute Python code in isolated containers
Agents Call other agents as sub-agents
Skills Retrieve instruction/knowledge documents
Queries Run SQL against external databases
Collections Search uploaded files
States Read and write persistent key-value data

Trigger Types

Functions and agents can be triggered in multiple ways:

  • API — Direct execution via the runtime API
  • Manual — Via the console UI
  • Agent — Called as a tool during a chat conversation
  • Webhook — Via an HTTP request to a configured endpoint
  • Schedule — Via a cron expression on a timer
  • CDC — Automatically when rows are inserted or updated in an external database table

Declarative Configuration

All resources can be defined in a YAML file and applied idempotently via the API or on startup. Config-managed resources are tracked with checksums for change detection. See Config Manager for details.


Authentication

Sinas uses email-based OTP authentication, with API keys available for programmatic access.

OTP Authentication

  1. Client sends email to POST /auth/login
  2. Sinas sends a 6-digit code to that email (valid for 10 minutes by default)
  3. Client submits code to POST /auth/verify-otp
  4. Sinas returns an access token (short-lived JWT, default 15 min) and a refresh token (long-lived, default 30 days)
  5. Use the access token in the Authorization: Bearer <token> header
  6. When the access token expires, use POST /auth/refresh to get a new one

Endpoints:

POST   /auth/login                # Send OTP to email
POST   /auth/verify-otp           # Verify OTP, receive tokens
POST   /auth/refresh              # Get new access token using refresh token
POST   /auth/logout               # Revoke refresh token
GET    /auth/me                   # Get current user info

API Keys

For programmatic access (scripts, CI/CD, integrations), create API keys instead of using short-lived JWT tokens. Each key has its own set of permissions (a subset of the creating user's permissions).

POST   /api/v1/api-keys           # Create key (plaintext returned once)
GET    /api/v1/api-keys           # List keys
GET    /api/v1/api-keys/{id}      # Get key details
DELETE /api/v1/api-keys/{id}      # Revoke key

API keys can be used via Authorization: Bearer <key> or X-API-Key: <key> headers. Keys can have optional expiration dates.


Role-Based Access Control (RBAC)

Overview

Users are assigned to roles, and roles define permissions. A user's effective permissions are the union of all permissions from all their roles (OR logic). Permissions are loaded from the database on every request — changes take effect immediately.

Default Roles

Role Description
Admins Full access to everything (sinas.*:all)
Users Create and manage own resources, chat with any agent, execute own functions
GuestUsers Read and update own profile only

Permission Format

<service>.<resource>[/<path>].<action>:<scope>

Components:

Part Description Examples
Service Top-level namespace sinas, or a custom prefix like titan, acme
Resource Resource type agents, functions, states, users
Path Optional namespace/name path /marketing/send_email, /*
Action What operation is allowed create, read, update, delete, execute, chat
Scope Ownership scope :own (user's resources), :all (all resources)

Permission Matching Rules

Scope hierarchy: :all automatically grants :own. A user with sinas.agents.read:all passes any check for sinas.agents.read:own.

Wildcards can be used at any level:

Pattern Matches
sinas.*:all Everything in Sinas (admin access)
sinas.agents/*/*.chat:all Chat with any agent in any namespace
sinas.functions/marketing/*.execute:own Execute any function in the marketing namespace
sinas.states/*.read:own Read own states in any namespace
sinas.chats.*:own All chat actions (read, update, delete) on own chats

Namespaced resource permissions use slashes in the resource path:

sinas.agents/support/ticket-bot.chat:own        # Chat with specific agent
sinas.functions/*/send_email.execute:own         # Execute send_email in any namespace
sinas.states/api_keys.read:all                   # Read all shared states in api_keys namespace

Non-namespaced resource permissions use simple dot notation:

sinas.webhooks.create:own                        # Create webhooks
sinas.schedules.read:own                         # Read own schedules
sinas.users.update:own                           # Update own profile

Custom Permissions

The permission system is not limited to sinas.*. You can define permissions with any service prefix for your own applications:

titan.student_profile.read:own
titan.courses/math/*.enroll:own
acme.billing.invoices.read:all
myapp.*:all

These work identically to built-in permissions — same wildcard matching, same scope hierarchy. This lets you use Sinas as the authorization backend for external applications.

Checking Permissions from External Services

Use the POST /auth/check-permissions endpoint to verify whether the current user (identified by their Bearer token or API key) has specific permissions:

curl -X POST https://yourdomain.com/auth/check-permissions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "permissions": ["titan.student_profile.read:own", "titan.courses.enroll:own"],
    "logic": "AND"
  }'

Response:

{
  "result": true,
  "logic": "AND",
  "checks": [
    {"permission": "titan.student_profile.read:own", "has_permission": true},
    {"permission": "titan.courses.enroll:own", "has_permission": true}
  ]
}
  • logic: "AND" — User must have ALL listed permissions (default)
  • logic: "OR" — User must have AT LEAST ONE of the listed permissions

This makes Sinas usable as a centralized authorization service for any number of external applications.

Managing Roles

POST   /api/v1/roles                        # Create role
GET    /api/v1/roles                        # List roles
GET    /api/v1/roles/{name}                 # Get role details
PATCH  /api/v1/roles/{name}                 # Update role
DELETE /api/v1/roles/{name}                 # Delete role
POST   /api/v1/roles/{name}/members         # Add user to role
DELETE /api/v1/roles/{name}/members/{id}    # Remove user from role
POST   /api/v1/roles/{name}/permissions     # Set permission
DELETE /api/v1/roles/{name}/permissions     # Remove permission
GET    /api/v1/permissions/reference        # List all known permissions

Runtime API vs Management API

Sinas has two API layers:

Runtime API (/)

The runtime API is mounted at the root. It handles authentication, chat, execution, state, file operations, and discovery. These are the endpoints your applications and end users interact with.

/auth/...              # Authentication
/agents/...            # Create chats with agents
/chats/...             # Send messages, manage chats
/functions/...         # Execute functions (sync and async)
/queries/...           # Execute database queries
/webhooks/...          # Trigger webhook-linked functions
/executions/...        # View execution history and results
/jobs/...              # Check job status
/states/...            # Key-value state storage
/files/...             # Upload, download, search files
/templates/...         # Render and send templates
/components/...        # Render components, proxy endpoints
/manifests/...         # Manifest status validation
/discovery/...         # List resources visible to the current user

Management API (/api/v1/)

The management API handles CRUD operations on all resources. These are typically used by admins, the console UI, and configuration tools.

/api/v1/agents/...                 # Agent CRUD
/api/v1/functions/...              # Function CRUD
/api/v1/skills/...                 # Skill CRUD
/api/v1/llm-providers/...         # LLM provider management (admin)
/api/v1/database-connections/...  # DB connection management (admin)
/api/v1/queries/...               # Query CRUD
/api/v1/collections/...           # Collection CRUD
/api/v1/templates/...             # Template CRUD
/api/v1/webhooks/...              # Webhook CRUD
/api/v1/schedules/...             # Schedule CRUD
/api/v1/components/...            # Component CRUD + compilation + share links
/api/v1/manifests/...             # Manifest CRUD
/api/v1/roles/...                 # Role & permission management
/api/v1/users/...                 # User management
/api/v1/api-keys/...              # API key management
/api/v1/dependencies/...          # Python dependency approval (admin)
/api/v1/packages/...              # Integration package management
/api/v1/workers/...               # Worker management (admin)
/api/v1/containers/...            # Container pool management (admin)
/api/v1/config/...                # Declarative config apply/validate/export (admin)
/api/v1/queue/...                 # Queue stats, job list, DLQ, cancel (admin)
/api/v1/request-logs/...          # Request log search (admin)

OpenAI SDK Adapter (/adapters/openai)

An OpenAI SDK-compatible API that maps to Sinas agents and LLM providers. Point any OpenAI SDK client at this endpoint to use Sinas agents.

POST   /adapters/openai/v1/chat/completions     # Chat completion (maps to agent or direct LLM)
GET    /adapters/openai/v1/models               # List available models (agents + provider models)
GET    /adapters/openai/v1/models/{model_id}    # Get model info

Agents are listed as models with names like agent:namespace/name. Provider models are listed with their provider prefix.

Interactive API Docs

Swagger UI is available at /docs (runtime API), /api/v1/docs (management API), and /adapters/openai/docs (OpenAI adapter) for exploring all endpoints and schemas interactively.

Discovery Endpoints

The discovery API returns resources visible to the current user, optionally filtered by app context:

GET    /discovery/agents           # Agents the user can chat with
GET    /discovery/functions        # Functions the user can see
GET    /discovery/skills           # Skills the user can see
GET    /discovery/collections      # Collections the user can access
GET    /discovery/templates        # Templates the user can use

Pass an app context via the X-Application header or ?app=namespace/name query parameter to filter results to a specific app's exposed namespaces.


Reference

Build

Agents

Agents are configurable AI assistants. Each agent has an LLM provider, a system prompt, and a set of enabled tools.

Key properties:

Property Description
namespace / name Unique identifier (e.g., support/ticket-agent)
llm_provider_id LLM provider to use (null = system default)
model Model override (null = provider's default)
system_prompt Jinja2 template for the system message
temperature Sampling temperature (default: 0.7)
max_tokens Max token limit for responses
input_schema JSON Schema for validating chat input variables
output_schema JSON Schema for validating agent output
initial_messages Few-shot example messages
enabled_functions Functions available as tools (list of namespace/name)
function_parameters Default parameter values per function (supports Jinja2)
enabled_agents Other agents callable as sub-agents
enabled_skills Skills available to the agent
enabled_queries Database queries available as tools
query_parameters Default query parameter values
enabled_collections File collections the agent can search
enabled_stores Stores the agent can access. List of {"store": "namespace/name", "access": "readonly"} or {"store": "namespace/name", "access": "readwrite"}
enabled_connectors Connectors available as tools. List of {"connector": "namespace/name", "operations": [...], "parameters": {"op_name": {"param": "value"}}}. Parameters support Jinja2 templates and are locked (hidden from LLM).
hooks Message lifecycle hooks. {"on_user_message": [...], "on_assistant_message": [...]}
icon Icon reference (see Icons)

Message hooks: Functions that run before/after agent messages. Each hook has:

  • function: reference to a function (namespace/name)
  • async: if true, fire-and-forget (no impact on latency)
  • on_timeout: block (stop pipeline) or passthrough (continue) — sync hooks only

Hook functions receive {"message": {"role": "...", "content": "..."}, "chat_id": "...", "agent": {"namespace": "...", "name": "..."}, "user_id": "..."} and can return:

  • {"content": "..."} to mutate the message
  • {"block": true, "reply": "..."} to stop the pipeline
  • null to pass through unchanged
agents:
  - name: my-agent
    hooks:
      onUserMessage:
        - function: default/guardrail
          async: false
          onTimeout: block
      onAssistantMessage:
        - function: default/pii-filter
          async: false
          onTimeout: passthrough

Management endpoints:

POST   /api/v1/agents                       # Create agent
GET    /api/v1/agents                       # List agents
GET    /api/v1/agents/{namespace}/{name}    # Get agent
PUT    /api/v1/agents/{namespace}/{name}    # Update agent
DELETE /api/v1/agents/{namespace}/{name}    # Delete agent

Runtime endpoints (chats):

POST   /agents/{namespace}/{name}/invoke             # Invoke (sync request/response)
POST   /agents/{namespace}/{name}/chats              # Create chat
GET    /chats                                        # List user's chats
GET    /chats/{id}                                   # Get chat with messages
PUT    /chats/{id}                                   # Update chat
DELETE /chats/{id}                                   # Delete chat
POST   /chats/{id}/messages                          # Send message
POST   /chats/{id}/messages/stream                   # Send message (SSE streaming)
GET    /chats/{id}/stream/{channel_id}               # Reconnect to active stream
POST   /chats/{id}/approve-tool/{tool_call_id}       # Approve/reject a tool call

Invoke endpoint: A synchronous request/response alternative to the two-step chat flow. Intended for integrations (Slack, Telegram, webhooks) that need a simple call-and-response.

# Simple invoke
curl -X POST https://yourdomain.com/agents/support/helper/invoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "What is the status of order #123?"}'
# → {"reply": "Your order is on its way...", "chat_id": "c_abc123"}

# With session key (conversation continuity)
curl -X POST https://yourdomain.com/agents/support/helper/invoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "Follow up on that", "session_key": "slack:U09ABC123"}'
# → Same chat_id as previous call with this session_key
  • session_key: Maps an external identifier (Slack channel, Telegram chat, WhatsApp number) to a persistent Sinas chat. One chat per (agent_id, session_key) pair.
  • reset: true: Archives the existing session and starts a new conversation.
  • input: Agent input variables, only used when creating a new chat.
  • Streams internally, returns assembled reply as a single JSON payload.

How chat works:

  1. Create a chat linked to an agent (optionally with input variables validated against input_schema)
  2. Send a message — Sinas builds the conversation context with the system prompt, preloaded skills, message history, and available tools
  3. The LLM generates a response, possibly calling tools
  4. If tools are called, Sinas executes them (in parallel where possible) and sends results back to the LLM for a follow-up response
  5. The final response is streamed to the client via Server-Sent Events

Ephemeral chats can be created with a TTL by passing expires_in (seconds) when creating the chat. Expired chats are automatically hard-deleted (with all messages) by a scheduled cleanup job:

# Create an ephemeral chat that expires in 1 hour
curl -X POST https://yourdomain.com/agents/default/default/chats \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"expires_in": 3600}'

Chat archiving — Chats can be archived via PUT /chats/{id} with {"archived": true}. Archived chats are hidden from the default list but can be included with ?include_archived=true.

Agent-to-agent calls go through the Redis queue so sub-agents run in separate workers, avoiding recursive blocking. Results stream back via Redis Streams.

Function parameter defaults pre-fill values when an agent calls a function. Supports Jinja2 templates referencing the agent's input variables:

{
  "email/send_email": {
    "sender": "{{company_email}}",
    "priority": "high"
  }
}

Functions

Functions are Python code that runs in isolated Docker containers. They can be used as agent tools, triggered by webhooks or schedules, or executed directly.

Key properties:

Property Description
namespace / name Unique identifier
code Python source code
description Shown to the LLM when used as an agent tool
input_schema JSON Schema for input validation
output_schema JSON Schema for output validation
shared_pool Run in shared container instead of sandbox container (admin-only)
requires_approval Require user approval when called by an agent
timeout Per-function timeout in seconds (overrides global FUNCTION_TIMEOUT, default: null)

Function signature

The entry point must be named handler. It receives two arguments — input_data (validated against input_schema) and context (execution metadata):

def handler(input_data, context):
    # input_data: dict validated against input_schema
    #
    # context dict — always present:
    # {
    #     "user_id":        str,   # ID of the user who triggered execution
    #     "user_email":     str,   # Email of the triggering user
    #     "access_token":   str,   # Short-lived JWT for calling the Sinas API
    #     "execution_id":   str,   # Unique execution ID
    #     "trigger_type":   str,   # "AGENT" | "API" | "WEBHOOK" | "SCHEDULE" | "CDC" | "HOOK" | "MANUAL"
    #     "chat_id":        str,   # Chat ID (when triggered by an agent, empty otherwise)
    #     "secrets":        dict,  # Decrypted secrets (shared pool only): {"NAME": "value"}
    # }

    return {"result": "value"}  # Must match output_schema

Legacy: Functions named after the resource name (e.g. def send_email(input_data, context)) still work but log a deprecation warning. Migrate to def handler(...).

The access_token lets functions call back into the Sinas API with the triggering user's identity — useful for reading state, triggering other functions, or accessing any other endpoint.

Trigger-specific input_data

Depending on how the function is invoked, input_data is populated differently:

Trigger input_data contents
AGENT / API / MANUAL / SCHEDULE Values matching your input schema, provided by the caller
WEBHOOK Webhook default_values merged with request body/query params
CDC {"table": "schema.table", "operation": "CHANGE", "rows": [...], "poll_column": str, "count": int, "timestamp": str}
HOOK {"message": {"role": "user"|"assistant", "content": "..."}, "chat_id": str, "agent": {"namespace": str, "name": str}, "session_key": str|null, "user_id": str}
Collection content filter {"content_base64": str, "namespace": str, "collection": str, "filename": str, "content_type": str, "size_bytes": int, "user_metadata": dict, "user_id": str}
Collection post-upload {"file_id": str, "namespace": str, "collection": str, "filename": str, "version": int, "file_path": str, "user_id": str, "metadata": dict}

Hook function return values

Functions used as message hooks (configured in agent's hooks field) can return:

Return value Effect
None or {} Pass through unchanged
{"content": "..."} Mutate the message content
{"block": true, "reply": "..."} Block the pipeline, return reply to client (sync hooks only)

Async hooks run fire-and-forget. For on_assistant_message async hooks, the return value retroactively updates the stored message (not the already-streamed response).

Interactive input (shared containers only)

Functions running in shared containers (shared_pool=true) can call input() to pause and wait for user input:

def handler(input_data, context):
    name = input("What is your name?")    # Pauses execution
    confirm = input(f"Confirm {name}?")   # Can call multiple times
    return {"name": name, "confirmed": confirm}

When input() is called:

  1. The execution status changes to AWAITING_INPUT with the prompt string
  2. The function thread blocks until a resume value is provided
  3. The calling agent or API client resumes execution with the user's response
  4. Multiple input() calls are supported (each triggers a new pause/resume cycle)

In sandbox containers (shared_pool=false), calling input() raises a RuntimeError.

Execution: Functions run in pre-warmed Docker containers from a managed pool. Input is validated before execution, output is validated after. All executions are logged with status, duration, input/output, and any errors.

Endpoints:

POST   /functions/{namespace}/{name}/execute               # Execute (sync, waits for result)
POST   /functions/{namespace}/{name}/execute/async         # Execute (async, returns execution_id)

POST   /api/v1/functions                                   # Create function
GET    /api/v1/functions                                   # List functions
GET    /api/v1/functions/{namespace}/{name}                # Get function
PUT    /api/v1/functions/{namespace}/{name}                # Update function
DELETE /api/v1/functions/{namespace}/{name}                # Delete function
GET    /api/v1/functions/{namespace}/{name}/versions       # List code versions

Secrets

Write-only credential store. Values are encrypted at rest and never returned via the API. Secrets are available in function context as context['secrets'] — only for shared pool (trusted) functions. Connectors resolve auth from secrets automatically.

Visibility:

  • shared (default) — global, available to all users and connectors
  • private — per-user, only used when that user triggers a connector or function

Private secrets override shared secrets with the same name. This enables multi-tenant patterns: admin sets a shared HUBSPOT_API_KEY, individual users can override with their own private key.

Endpoints:

POST   /api/v1/secrets                  # Create or update (upsert by name+visibility)
GET    /api/v1/secrets                  # List names and descriptions (no values)
GET    /api/v1/secrets/{name}           # Get metadata (no value)
PUT    /api/v1/secrets/{name}           # Update value or description
DELETE /api/v1/secrets/{name}           # Delete

Access at runtime (shared pool functions only):

def my_function(input, context):
    # Private secrets override shared for the calling user
    token = context['secrets']['SLACK_BOT_TOKEN']

YAML config:

secrets:
  - name: SLACK_BOT_TOKEN
    value: xoxb-...          # omit to skip value update on re-apply
    description: Slack bot OAuth token
    # visibility defaults to "shared" in YAML config

Connectors

Named HTTP client configurations with typed operations. Executed in-process in the backend (no container overhead). Operations are exposed as agent tools. Auth resolved from the Secrets store at call time.

Endpoints:

POST   /api/v1/connectors                                        # Create connector
GET    /api/v1/connectors                                        # List connectors
GET    /api/v1/connectors/{namespace}/{name}                     # Get connector
PUT    /api/v1/connectors/{namespace}/{name}                     # Update connector
DELETE /api/v1/connectors/{namespace}/{name}                     # Delete connector
POST   /api/v1/connectors/parse-openapi                          # Parse OpenAPI spec (no connector required)
POST   /api/v1/connectors/{namespace}/{name}/import-openapi      # Import operations from OpenAPI spec into connector
POST   /api/v1/connectors/{namespace}/{name}/test/{operation}    # Test an operation

Auth types: bearer, basic, api_key, sinas_token (forwards caller's JWT), none

Auth is resolved from the Secrets store. Private secrets override shared for the calling user — enabling multi-tenant patterns where each user can have their own API key for the same connector.

Agent configuration:

agents:
  - name: slack-bot
    enabledConnectors:
      - connector: default/slack-api
        operations: [post_message, get_channel_info]
        parameters:
          post_message:
            channel: "{{ default_channel }}"

YAML config:

connectors:
  - name: slack-api
    namespace: default
    baseUrl: https://slack.com/api
    auth:
      type: bearer
      secret: SLACK_BOT_TOKEN
    operations:
      - name: post_message
        method: POST
        path: /chat.postMessage
        parameters:
          type: object
          properties:
            channel: { type: string }
            text: { type: string }
          required: [channel, text]

Execution history:

GET    /executions                               # List executions (filterable by chat_id, trigger_type, status)
GET    /executions/{execution_id}                # Get execution details (includes tool_call_id link)
POST   /executions/{execution_id}/continue       # Resume a paused execution with user input

Executions include a tool_call_id field linking them to the tool call that triggered them, enabling execution tree visualization in the Logs page.

Input schema presets: The function editor includes built-in presets for common input/output schemas. Use the "Load preset" dropdown when editing schemas:

Preset Use case
Pre-upload filter Content filtering before file upload (receives file content, returns approved/rejected)
Post-upload Processing after successful file upload (receives file_id, metadata)
CDC (Change Data Capture) Processing database changes (receives table, rows, poll_column, count)
Message Hook Message lifecycle hook (receives message, chat_id, agent; returns content mutation or block)

Components

Components are embeddable UI widgets built with JSX/HTML/JS and compiled by Sinas into browser-ready bundles. They can call agents, functions, queries, and access state through proxy endpoints.

Key properties:

Property Description
namespace / name Unique identifier
title Display title
source_code JSX/HTML/JS source
compiled_bundle Auto-generated browser-ready JS
input_schema JSON Schema for component configuration
enabled_agents Agents the component can call
enabled_functions Functions the component can call
enabled_queries Queries the component can execute
enabled_components Other components it can embed
enabled_stores Stores the component can access ({"store": "ns/name", "access": "readonly|readwrite"})
css_overrides Custom CSS
visibility private, shared, or public

Components use the sinas-ui library (loaded from npm/unpkg) for a consistent look and feel.

Management endpoints:

POST   /api/v1/components                                  # Create component
GET    /api/v1/components                                  # List components
GET    /api/v1/components/{namespace}/{name}               # Get component
PUT    /api/v1/components/{namespace}/{name}               # Update component
DELETE /api/v1/components/{namespace}/{name}               # Delete component
POST   /api/v1/components/{namespace}/{name}/compile       # Trigger compilation

Share links allow embedding components outside Sinas with optional expiration and view limits:

POST   /api/v1/components/{namespace}/{name}/shares        # Create share link
GET    /api/v1/components/{namespace}/{name}/shares        # List share links
DELETE /api/v1/components/{namespace}/{name}/shares/{token} # Revoke share link

Runtime rendering:

GET    /components/{namespace}/{name}/render               # Render as full HTML page
GET    /components/shared/{token}                          # Render via share token

Proxy endpoints allow components to call backend resources from the browser securely — the proxy enforces the component's enabled_* permissions:

POST   /components/{ns}/{name}/proxy/queries/{q_ns}/{q_name}/execute    # Execute query
POST   /components/{ns}/{name}/proxy/functions/{fn_ns}/{fn_name}/execute # Execute function
POST   /components/{ns}/{name}/proxy/states/{state_ns}                   # Access state

Queries

Queries are saved SQL templates that can be executed directly or used as agent tools.

Key properties:

Property Description
namespace / name Unique identifier
database_connection_id Which database connection to use
description Shown to the LLM as the tool description
operation read or write
sql SQL with :param_name placeholders
input_schema JSON Schema for parameter validation
output_schema JSON Schema for output validation
timeout_ms Query timeout (default: 5000ms)
max_rows Max rows returned for read operations (default: 1000)

Agent query parameters support defaults and locking:

query_parameters:
  "analytics/user_orders":
    "user_id":
      value: "{{user_id}}"    # Jinja2 template from agent input
      locked: true             # Hidden from LLM, always injected
    "status":
      value: "pending"
      locked: false            # Shown to LLM with default, LLM can override

Locked parameters prevent the LLM from seeing or modifying security-sensitive values (like user_id).

Contextual parameters: The following parameters are automatically injected into every query execution and can be referenced in SQL:

Parameter Description
:user_id UUID of the user who triggered the query
:user_email Email of the triggering user

These are always available regardless of the query's input_schema. Use them for row-level security:

-- Only return orders belonging to the calling user
SELECT * FROM orders WHERE created_by = :user_id

-- Audit trail
INSERT INTO audit_log (action, performed_by) VALUES (:action, :user_email)

Endpoints:

POST   /queries/{namespace}/{name}/execute            # Execute with parameters (runtime)

POST   /api/v1/queries                              # Create query
GET    /api/v1/queries                              # List queries
GET    /api/v1/queries/{namespace}/{name}           # Get query
PUT    /api/v1/queries/{namespace}/{name}           # Update query
DELETE /api/v1/queries/{namespace}/{name}           # Delete query

Configure

Skills

Skills are reusable instruction documents that give agents specialized knowledge or guidelines.

Key properties:

Property Description
namespace / name Unique identifier
description What the skill helps with (shown to the LLM as the tool description)
content Markdown instructions

Two modes:

Mode Behavior Best for
Preloaded (preload: true) Injected into the system prompt Tone guidelines, safety rules, persona traits
Progressive (preload: false) Exposed as a tool the LLM calls when needed Research methods, domain expertise, task-specific instructions

Example agent configuration:

enabled_skills:
  - skill: "default/tone_guidelines"
    preload: true       # Always present in system prompt
  - skill: "default/web_research"
    preload: false      # LLM decides when to retrieve it

Endpoints:

POST   /api/v1/skills                       # Create skill
GET    /api/v1/skills                       # List skills
GET    /api/v1/skills/{namespace}/{name}    # Get skill
PUT    /api/v1/skills/{namespace}/{name}    # Update skill
DELETE /api/v1/skills/{namespace}/{name}    # Delete skill

LLM Providers

LLM providers connect Sinas to language model APIs.

Supported providers:

Type Description
openai OpenAI API (GPT-4, GPT-4o, o1, etc.) and OpenAI-compatible endpoints
anthropic Anthropic API (Claude 3, Claude 4, etc.)
mistral Mistral AI (Mistral Large, Pixtral, etc.)
ollama Local models via Ollama

Key properties:

Property Description
name Unique provider name
provider_type openai, anthropic, mistral, or ollama
api_key API key (encrypted at rest, never returned in API responses)
api_endpoint Custom endpoint URL (required for Ollama, useful for proxies)
default_model Model used when agents don't specify one
config Additional settings (e.g., max_tokens, organization_id)
is_default Whether this is the system-wide default provider

Provider resolution for agents:

  1. Agent's explicit llm_provider_id if set
  2. Agent's model field with the resolved provider
  3. Provider's default_model
  4. System default provider as final fallback

Endpoints (admin only):

POST   /api/v1/llm-providers             # Create provider
GET    /api/v1/llm-providers             # List providers
GET    /api/v1/llm-providers/{name}      # Get provider
PATCH  /api/v1/llm-providers/{id}        # Update provider
DELETE /api/v1/llm-providers/{id}        # Delete provider

Database Connections

Database connections store credentials and manage connection pools for external databases.

Supported databases: PostgreSQL, ClickHouse, Snowflake

Key properties:

Property Description
name Unique connection name
connection_type postgresql, clickhouse, or snowflake
host, port, database, username, password Connection details
ssl_mode Optional SSL configuration
config Pool settings (min_pool_size, max_pool_size)

Passwords are encrypted at rest. Connection pools are managed automatically and invalidated when settings change.

Endpoints (admin only):

POST   /api/v1/database-connections                    # Create connection
GET    /api/v1/database-connections                    # List connections
GET    /api/v1/database-connections/{name}             # Get by name
PATCH  /api/v1/database-connections/{id}               # Update
DELETE /api/v1/database-connections/{id}               # Delete
POST   /api/v1/database-connections/test               # Test raw connection params
POST   /api/v1/database-connections/{id}/test          # Test saved connection

Templates

Templates are Jinja2-based documents for emails, notifications, and dynamic content.

Key properties:

Property Description
namespace / name Unique identifier
title Optional title template (e.g., email subject)
html_content Jinja2 HTML template
text_content Optional plain-text fallback
variable_schema JSON Schema for validating template variables

HTML output is auto-escaped to prevent XSS. Missing variables cause errors (strict mode).

Management endpoints:

POST   /api/v1/templates                                   # Create template
GET    /api/v1/templates                                   # List templates
GET    /api/v1/templates/{id}                              # Get by ID
GET    /api/v1/templates/by-name/{namespace}/{name}        # Get by name
PATCH  /api/v1/templates/{id}                              # Update
DELETE /api/v1/templates/{id}                              # Delete

Runtime endpoints:

POST   /templates/{id}/render        # Render template with variables
POST   /templates/{id}/send          # Render and send as email

Triggers

Webhooks

Webhooks expose functions as HTTP endpoints. When a request arrives at a webhook path, Sinas executes the linked function with the request data.

Key properties:

Property Description
path URL path (e.g., stripe/payment-webhook)
http_method GET, POST, PUT, DELETE, or PATCH
function_namespace / function_name Target function
requires_auth Whether the caller must provide a Bearer token
default_values Default parameters merged with request data (request takes priority)

How input is extracted:

  • POST/PUT/PATCH with JSON body → body becomes the input
  • GET → query parameters become the input
  • Default values are merged underneath (request data overrides)

Example:

# Create a webhook
curl -X POST https://yourdomain.com/api/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "stripe/payment",
    "function_namespace": "payments",
    "function_name": "process_webhook",
    "http_method": "POST",
    "requires_auth": false,
    "default_values": {"source": "stripe"}
  }'

# Trigger it
curl -X POST https://yourdomain.com/webhooks/stripe/payment \
  -H "Content-Type: application/json" \
  -d '{"event": "charge.succeeded", "amount": 1000}'

# Function receives: {"source": "stripe", "event": "charge.succeeded", "amount": 1000}

Endpoints:

POST   /api/v1/webhooks              # Create webhook
GET    /api/v1/webhooks              # List webhooks
GET    /api/v1/webhooks/{path}       # Get webhook
PATCH  /api/v1/webhooks/{path}       # Update webhook
DELETE /api/v1/webhooks/{path}       # Delete webhook

Schedules

Schedules trigger functions or agents on a cron timer.

Key properties:

Property Description
name Unique name (per user)
schedule_type function or agent
target_namespace / target_name Function or agent to trigger
cron_expression Standard cron expression (e.g., 0 9 * * MON-FRI)
timezone Schedule timezone (default: UTC)
input_data Input passed to the function or agent
content Message content (agent schedules only)

For agent schedules, a new chat is created for each run with the schedule name and timestamp as the title.

Endpoints:

POST   /api/v1/schedules              # Create schedule
GET    /api/v1/schedules              # List schedules
GET    /api/v1/schedules/{name}       # Get schedule
PATCH  /api/v1/schedules/{name}       # Update schedule
DELETE /api/v1/schedules/{name}       # Delete schedule

Database Triggers (CDC)

Database triggers watch external database tables for changes and automatically execute functions when new or updated rows are detected. This is poll-based Change Data Capture — no setup required on the source database.

How it works:

  1. A separate CDC service polls the configured table at a fixed interval
  2. It queries rows where poll_column > last_bookmark (e.g., updated_at > '2026-03-01T10:00:00')
  3. If new rows are found, they are batched into a single function call
  4. The bookmark advances to the highest value in the batch
  5. On first activation, the bookmark is set to MAX(poll_column) — no backfill of existing data

Key properties:

Property Description
name Unique trigger name (per user)
database_connection_id Which database connection to poll
schema_name Database schema (default: public)
table_name Table to watch
operations ["INSERT"], ["UPDATE"], or both
function_namespace / function_name Function to execute when changes are detected
poll_column Monotonically increasing column used as bookmark (e.g., updated_at, id)
poll_interval_seconds How often to poll (1–3600, default: 10)
batch_size Max rows per poll (1–10000, default: 100)
is_active Enable/disable without deleting
last_poll_value Current bookmark (managed automatically)
error_message Last error, if any (visible in UI)

The poll_column must be a column whose value only increases — timestamps, auto-increment IDs, or sequences. The column type is detected automatically and comparisons are cast to the correct type.

Function input payload:

When changes are detected, the target function receives all new rows in a single call:

{
  "table": "public.orders",
  "operation": "CHANGE",
  "rows": [
    {"id": 123, "status": "paid", "amount": 99.50, "updated_at": "2026-03-02T10:30:00Z"},
    {"id": 124, "status": "pending", "amount": 45.00, "updated_at": "2026-03-02T10:30:01Z"}
  ],
  "poll_column": "updated_at",
  "count": 2,
  "timestamp": "2026-03-02T10:30:05Z"
}

If a poll returns zero rows, no function call is made.

Error handling: On failure, the trigger logs the error to error_message and retries with exponential backoff (up to 60 seconds). The trigger continues retrying until deactivated or the issue is resolved.

Limitations:

  • Cannot detect DELETE operations (poll-based limitation)
  • Changes are detected with a delay equal to the poll interval
  • The poll_column must never decrease — resetting it will cause missed or duplicate rows

Endpoints:

POST   /api/v1/database-triggers              # Create trigger
GET    /api/v1/database-triggers              # List triggers
GET    /api/v1/database-triggers/{name}       # Get trigger
PATCH  /api/v1/database-triggers/{name}       # Update trigger
DELETE /api/v1/database-triggers/{name}       # Delete trigger

Declarative configuration:

databaseTriggers:
  - name: "customer_changes"
    connectionName: "prod_database"
    tableName: "customers"
    operations: ["INSERT", "UPDATE"]
    functionName: "sync/process_customer"
    pollColumn: "updated_at"
    pollIntervalSeconds: 10
    batchSize: 100

Storage

Collections & Files

Collections are containers for file uploads with versioning, metadata validation, and processing hooks.

Collection properties:

Property Description
namespace / name Unique identifier
metadata_schema JSON Schema that file metadata must conform to
content_filter_function Function that runs on upload to approve/reject files
post_upload_function Function that runs after upload for processing
max_file_size_mb Per-file size limit (default: 100 MB)
max_total_size_gb Total collection size limit (default: 10 GB)

File features:

  • Versioning — Every upload creates a new version. Previous versions are preserved.
  • Metadata — Each file carries JSON metadata validated against the collection's schema.
  • Visibility — Files can be private (owner only) or shared (users with collection :all access).
  • Content filtering — Optional function runs on upload that can approve, reject, or modify the file.

Management endpoints:

POST   /api/v1/collections                              # Create collection
GET    /api/v1/collections                              # List collections
GET    /api/v1/collections/{namespace}/{name}           # Get collection
PUT    /api/v1/collections/{namespace}/{name}           # Update
DELETE /api/v1/collections/{namespace}/{name}           # Delete (cascades to files)

Runtime file endpoints:

POST   /files/{namespace}/{collection}                   # Upload file
GET    /files/{namespace}/{collection}                   # List files
GET    /files/{namespace}/{collection}/{filename}        # Download file
PATCH  /files/{namespace}/{collection}/{filename}        # Update metadata
DELETE /files/{namespace}/{collection}/{filename}        # Delete file
POST   /files/{namespace}/{collection}/{filename}/url    # Generate temporary download URL
POST   /files/{namespace}/{collection}/search            # Search files

States

States are a persistent key-value store organized by namespace. Agents use states to maintain memory and context across conversations.

Key properties:

Property Description
namespace Organizational grouping (e.g., preferences, memory, api_keys)
key Unique key within user + namespace
value Any JSON data
visibility private (owner only) or shared (users with namespace :all permission)
description Optional description
tags Tags for filtering and search
relevance_score Priority for context retrieval (0.0–1.0, default: 1.0)
encrypted If true, value is encrypted at rest with Fernet and decrypted on read
expires_at Optional expiration time

Encrypted states: Set encrypted: true when creating or updating a state to store the value encrypted. The plaintext value is stored in encrypted_value (Fernet-encrypted) while value is set to {}. On read, the value is transparently decrypted. This is useful for storing API keys, tokens, or other secrets.

Agent state access is declared per agent via enabled_stores:

enabledStores:
  - store: "shared_knowledge/main"
    access: readonly
  - store: "conversation_memory/main"
    access: readwrite

Read-only stores give the agent a retrieve_context tool. Read-write stores additionally provide save_context, update_context, and delete_context.

Endpoints:

POST   /states              # Create state entry
GET    /states              # List (supports namespace, visibility, tags, search filters)
GET    /states/{id}         # Get state
PUT    /states/{id}         # Update state
DELETE /states/{id}         # Delete state

Admin

Integration Packages

Integration packages bundle agents, functions, skills, components, templates, and other resources into a shareable YAML file that can be installed with one click.

How packages work:

  1. Create: Select resources from your Sinas instance → export as SinasPackage YAML
  2. Share: Distribute the YAML file (GitHub, email, package registry)
  3. Install: Paste/upload the YAML → preview changes → confirm install
  4. Uninstall: Removes all resources created by the package in one operation

Package YAML format:

apiVersion: sinas.co/v1
kind: SinasPackage
package:
  name: crm-integration
  version: "1.0.0"
  description: "CRM support agents and functions"
  author: "team@company.com"
  url: "https://github.com/company/sinas-crm"
spec:
  agents: [...]
  functions: [...]
  skills: [...]
  components: [...]
  templates: [...]
  queries: [...]
  collections: [...]
  webhooks: [...]
  schedules: [...]
  manifests: [...]

Key behaviors:

  • Resources created by packages are tagged with managed_by: "pkg:<name>"
  • Detach-on-edit: Editing a package-managed resource clears managed_by — the resource survives uninstall
  • Uninstall: Deletes all resources where managed_by = "pkg:<name>" + the package record
  • Excluded types: Packages cannot include roles, users, LLM providers, or database connections (these are environment-specific)

Endpoints:

POST   /api/v1/packages/install       # Install package from YAML
POST   /api/v1/packages/preview       # Preview install (dry run)
POST   /api/v1/packages/create        # Create package YAML from selected resources
GET    /api/v1/packages               # List installed packages
GET    /api/v1/packages/{name}        # Get package details
DELETE /api/v1/packages/{name}        # Uninstall package
GET    /api/v1/packages/{name}/export # Export original YAML

Creating a package from existing resources:

curl -X POST https://yourdomain.com/api/v1/packages/create \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-package",
    "version": "1.0.0",
    "description": "My integration package",
    "resources": [
      {"type": "agent", "namespace": "support", "name": "ticket-bot"},
      {"type": "function", "namespace": "support", "name": "lookup-customer"},
      {"type": "template", "namespace": "support", "name": "ticket-reply"},
      {"type": "schedule", "namespace": "default", "name": "daily-digest"}
    ]
  }'

Supported resource types: agent, function, skill, manifest, component, query, collection, template, webhook, schedule.

Manifests

Manifests are application declarations that describe what resources, permissions, and stores an application built on Sinas requires. They enable a single API call to validate whether a user has everything needed to run the application.

Key properties:

Property Description
namespace / name Unique identifier
description What this manifest declares
required_resources Resource references: [{"type": "agent", "namespace": "...", "name": "..."}]
required_permissions Permissions the application needs
optional_permissions Optional permissions for extended features
exposed_namespaces Namespace filter per resource type (e.g., {"agents": ["support"]})
store_dependencies Stores the application expects: [{"store": "ns/name", "key": "optional_key"}]

Endpoints:

POST   /api/v1/manifests                              # Create manifest
GET    /api/v1/manifests                              # List manifests
GET    /api/v1/manifests/{namespace}/{name}           # Get manifest
PUT    /api/v1/manifests/{namespace}/{name}           # Update
DELETE /api/v1/manifests/{namespace}/{name}           # Delete

Runtime status validation:

GET    /api/runtime/manifests/{namespace}/{name}/status   # Validate dependencies

Returns ready: true/false with details on satisfied/missing resources, granted/missing permissions, and existing/missing store dependencies.

Users & Roles

Users are identified by email. They can be created automatically on first login (OTP or OIDC) or provisioned via the management API or declarative config.

Roles group permissions together. Users can belong to multiple roles. All permissions across all of a user's roles are combined.

User endpoints:

GET    /api/v1/users                    # List users (admin)
POST   /api/v1/users                    # Create user (admin)
GET    /api/v1/users/{id}              # Get user
PATCH  /api/v1/users/{id}              # Update user
DELETE /api/v1/users/{id}              # Delete user (admin)

Role endpoints: See Managing Roles.

Permissions

See Role-Based Access Control (RBAC) for the full permission system documentation, including format, matching rules, custom permissions, and the check-permissions endpoint.

Quick reference of action verbs:

Verb Usage
create Create a resource
read View/list a resource
update Modify a resource
delete Remove a resource
execute Run a function or query
chat Chat with an agent
render Render a template
send Send a rendered template
upload Upload a file
download Download a file
install Approve a package

System Workers & Containers

Sinas has a dual-execution model for functions, plus dedicated queue workers for async job processing.

Sandbox Containers

The sandbox container pool is a set of pre-warmed, generic Docker containers for executing untrusted user code. This is the default execution mode for all functions (shared_pool=false).

How it works:

  • On startup, the pool creates sandbox_min_size containers (default: 4) ready to accept work.
  • When a function executes, a container is acquired from the idle pool, used, and returned.
  • Containers are recycled (destroyed and replaced) after sandbox_max_executions uses (default: 100) to prevent state leakage between executions.
  • If a container errors during execution, it's marked as tainted and destroyed immediately.
  • A background replenishment loop monitors the idle count and creates new containers whenever it drops below sandbox_min_idle (default: 2), up to sandbox_max_size (default: 20).
  • Health checks run every 60 seconds to detect and replace dead containers.

Isolation guarantees:

Each container runs with strict resource limits and security hardening:

Constraint Default
Memory 512 MB (MAX_FUNCTION_MEMORY)
CPU 1.0 cores (MAX_FUNCTION_CPU)
Disk 1 GB (MAX_FUNCTION_STORAGE)
Execution time 300 seconds (FUNCTION_TIMEOUT)
Temp storage 100 MB tmpfs at /tmp
Capabilities All dropped, only CHOWN/SETUID/SETGID added
Privilege escalation Disabled (no-new-privileges)

Runtime scaling:

The pool can be scaled up or down at runtime without restarting the application:

# Check current pool state
GET /api/v1/containers/stats
# → {"idle": 2, "in_use": 3, "total": 5, "max_size": 20, ...}

# Scale up for high load
POST /api/v1/containers/scale
{"target": 15}
# → {"action": "scale_up", "previous": 5, "current": 15, "added": 10}

# Scale back down (only removes idle containers — never interrupts running executions)
POST /api/v1/containers/scale
{"target": 4}
# → {"action": "scale_down", "previous": 15, "current": 4, "removed": 11}

Package installation:

When new packages are approved, existing containers don't have them yet. Use the reload endpoint to install approved packages into all idle containers:

POST /api/v1/containers/reload
# → {"status": "completed", "idle_containers": 4, "success": 4, "failed": 0}

Containers that are currently executing are unaffected. New containers created by the replenishment loop automatically include all approved packages.

Shared Containers

Functions marked shared_pool=true run in persistent shared containers instead of sandbox containers. This is an admin-only option for trusted code that benefits from longer-lived containers.

Differences from sandbox:

Sandbox Containers Shared Containers
Trust level Untrusted user code Trusted admin code only
Isolation Per-request (recycled after N uses) Shared (persistent containers)
Lifecycle Created/destroyed automatically Persist until explicitly scaled down
Scaling Auto-replenishment + manual Manual via API only
Load balancing First available idle container Round-robin across workers
Best for User-submitted functions Admin functions, long-startup libraries

When to use shared_pool=true (shared containers):

  • Functions created and maintained by admins (not user-submitted code)
  • Functions that import heavy libraries (pandas, scikit-learn) where container startup cost matters
  • Performance-critical functions that benefit from warm containers

Management:

# List workers
GET /api/v1/workers

# Check count
GET /api/v1/workers/count
# → {"count": 4}

# Scale workers
POST /api/v1/workers/scale
{"target_count": 6}
# → {"action": "scale_up", "previous_count": 4, "current_count": 6, "added": 2}

# Reload packages in all workers
POST /api/v1/workers/reload
# → {"status": "completed", "total_workers": 6, "success": 6, "failed": 0}

Queue Workers

All function and agent executions are processed asynchronously through Redis-based queues (arq). Two separate worker types handle different workloads:

Worker Docker service Queue Concurrency Retries
Function workers queue-worker sinas:queue:functions 10 jobs/worker Up to 3
Agent workers queue-agent sinas:queue:agents 5 jobs/worker None (not idempotent)

Function workers dequeue function execution jobs, route them to either sandbox or shared containers, track results in Redis, and handle retries. Failed jobs that exhaust retries are moved to a dead letter queue (DLQ) for inspection and manual retry.

Agent workers handle chat message processing — they call the LLM, execute tool calls, and stream responses back via Redis Streams. Agent jobs don't retry because LLM calls with tool execution have side effects.

Scaling is controlled via Docker Compose replicas:

# docker-compose.yml
queue-worker:
  command: python -m arq app.queue.worker.WorkerSettings
  deploy:
    replicas: ${QUEUE_WORKER_REPLICAS:-2}

queue-agent:
  command: python -m arq app.queue.worker.AgentWorkerSettings
  deploy:
    replicas: ${QUEUE_AGENT_REPLICAS:-2}

Each worker sends a heartbeat to Redis every 10 seconds (TTL: 30 seconds). If a worker dies, its heartbeat key auto-expires, making it easy to detect dead workers.

Job status tracking:

# Check job status
GET /jobs/{job_id}
# → {"status": "completed", "execution_id": "...", ...}

# Get job result
GET /jobs/{job_id}/result
# → {function output}

Jobs go through states: queuedrunningcompleted or failed. Stale or orphaned jobs can be cancelled via the admin API:

# Cancel a running or queued job
POST /api/v1/queue/jobs/{job_id}/cancel
# → {"status": "cancelled", "job_id": "..."}

Cancellation updates the Redis status to cancelled and marks the DB execution record as CANCELLED. It also publishes to the done channel so any waiters unblock. This is a soft cancel — it does not kill running containers.

Results are stored in Redis with a 24-hour TTL.

System Endpoints

Admin endpoints for monitoring and managing the Sinas deployment. All require sinas.system.read:all or sinas.system.update:all permissions.

Health check:

GET /api/v1/system/health

Returns a comprehensive health report:

  • services — All Docker Compose containers with status, health, uptime, CPU %, and memory usage. Infrastructure containers (redis, postgres, pgbouncer) are listed first, followed by application containers sorted alphabetically. Sandbox and shared worker containers are included.
  • host — Host-level CPU, memory, and disk usage (read from /proc on Linux).
  • warnings — Auto-generated alerts at three levels:
    • critical — No queue workers running, or infrastructure services (redis, postgres, pgbouncer) down
    • warning — Non-infrastructure services down, unhealthy containers, DLQ items, queue backlog >50, disk/memory >90%
    • info — Disk/memory >75%

Container restart:

POST /api/v1/system/containers/{container_name}/restart
# → {"status": "restarted", "container": "sinas-backend"}

Restarts any Docker container by name (15-second timeout). Returns 404 if the container doesn't exist.

Flush stuck jobs:

POST /api/v1/system/flush-stuck-jobs

Cancels all jobs that have been stuck in running state for over 2 hours. Useful for recovering from worker crashes or orphaned jobs.

Dependencies (Python Packages)

Functions can only use Python packages that have been approved by an admin. This prevents untrusted code from installing arbitrary dependencies.

Approval flow:

  1. Admin approves a dependency (optionally pinning a version)
  2. Package becomes available in newly created containers and workers
  3. Use POST /containers/reload or POST /workers/reload to install into existing containers
POST   /api/v1/dependencies              # Approve dependency (admin)
GET    /api/v1/dependencies              # List approved dependencies
DELETE /api/v1/dependencies/{id}         # Remove approval (admin)

Optionally restrict which packages can be approved with a whitelist:

# In .env — only these packages can be approved
ALLOWED_PACKAGES=requests,pandas,numpy,redis,boto3

Configuration Reference

Container pool:

Variable Default Description
POOL_MIN_SIZE 4 Containers created on startup
POOL_MAX_SIZE 20 Maximum total containers
POOL_MIN_IDLE 2 Replenish when idle count drops below this
POOL_MAX_EXECUTIONS 100 Recycle container after this many uses
POOL_ACQUIRE_TIMEOUT 30 Seconds to wait for an available container

Function execution:

Variable Default Description
FUNCTION_TIMEOUT 300 Max execution time in seconds
MAX_FUNCTION_MEMORY 512 Memory limit per container (MB)
MAX_FUNCTION_CPU 1.0 CPU cores per container
MAX_FUNCTION_STORAGE 1g Disk storage limit
FUNCTION_CONTAINER_IDLE_TIMEOUT 3600 Idle container cleanup (seconds)

Workers and queues:

Variable Default Description
DEFAULT_WORKER_COUNT 4 Shared workers created on startup
QUEUE_WORKER_REPLICAS 2 Function queue worker processes
QUEUE_AGENT_REPLICAS 2 Agent queue worker processes
QUEUE_FUNCTION_CONCURRENCY 10 Concurrent jobs per function worker
QUEUE_AGENT_CONCURRENCY 5 Concurrent jobs per agent worker
QUEUE_MAX_RETRIES 3 Retry attempts before DLQ
QUEUE_RETRY_DELAY 10 Seconds between retries

Packages:

Variable Default Description
ALLOW_PACKAGE_INSTALLATION true Enable pip in containers
ALLOWED_PACKAGES (empty) Comma-separated whitelist (empty = all allowed)

Icons

Agents and functions support configurable icons via the icon field. Two formats are supported:

Format Example Description
url:<url> url:https://example.com/icon.png Direct URL to an image
collection:<ns>/<coll>/<file> collection:assets/icons/bot.png File stored in a Sinas collection

Collection-based icons generate signed JWT URLs for private files and direct URLs for public collection files. Icons are resolved at read time via the icon resolver service.

Config Manager

The config manager supports GitOps-style declarative configuration. Define all your resources in a YAML file and apply it idempotently.

YAML structure:

apiVersion: sinas.co/v1
kind: SinasConfig
metadata:
  name: my-config
  description: Production configuration
spec:
  roles:               # Roles and permissions
  users:               # User provisioning
  llmProviders:        # LLM provider connections
  databaseConnections: # External database credentials
  dependencies:        # Python packages (pip)
  secrets:             # Encrypted credentials (values omitted on export)
  connectors:          # HTTP connectors with typed operations
  skills:              # Instruction documents
  components:          # UI components
  functions:           # Python functions
  queries:             # Saved SQL templates
  collections:         # File storage collections
  templates:           # Jinja2 templates
  stores:              # State store definitions
  manifests:           # Application manifests
  agents:              # AI agent configurations
  webhooks:            # HTTP triggers for functions
  schedules:           # Cron-based triggers
  databaseTriggers:    # CDC polling triggers

All sections are optional — include only what you need.

Key behaviors:

  • Idempotent — Applying the same config twice does nothing. Unchanged resources are skipped (SHA256 checksum comparison).
  • Config-managed tracking — Resources created via config are tagged with managed_by: "config". The system won't overwrite resources that were created manually (it warns instead).
  • Environment variable interpolation — Use ${VAR_NAME} in values (e.g., apiKey: "${OPENAI_API_KEY}").
  • Reference validation — Cross-references (e.g., an agent referencing a function) are validated before applying.
  • Dry run — Set dryRun: true to preview changes without applying.

Endpoints (admin only):

POST   /api/v1/config/validate       # Validate YAML syntax and references
POST   /api/v1/config/apply          # Apply config (supports dryRun and force flags)
GET    /api/v1/config/export         # Export current configuration as YAML

Auto-apply on startup:

# In .env
CONFIG_FILE=config/production.yaml
AUTO_APPLY_CONFIG=true

Apply via API:

curl -X POST https://yourdomain.com/api/v1/config/apply \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"config\": \"$(cat config.yaml)\", \"dryRun\": false}"