The OpenClaw WebOS uses a PostgreSQL-backed storage layer that provides CRUD operations for projects, tasks, workflows, dependencies, and all related entities.
Source: storage/asana.js
The storage layer is implemented as a single module (storage/asana.js) that exports an AsanaStorage class. It wraps a PostgreSQL connection pool and provides methods for all dashboard data operations.
const storage = new AsanaStorage({
host: 'localhost',
port: 5432,
database: 'openclaw_dashboard',
user: 'openclaw',
password: 'change-me'
});Connection parameters are read from environment variables (see configuration-reference.md).
| Method | Description |
|---|---|
listProjects(options) |
List projects with pagination, filtering by status/tags |
getProject(id) |
Get a single project by ID |
createProject(data) |
Create a new project |
updateProject(id, data) |
Update project fields |
deleteProject(id) |
Delete a project (cascade deletes tasks) |
getProjectByNamespace(namespace) |
Find project by QMD namespace |
Options for listProjects:
status— Filter by status (active,paused,archived)tags— Filter by tags (array)limit— Page size (max 200)offset— Pagination offsetsort— Sort field (name,created_at,updated_at)order— Sort direction (asc,desc)
| Method | Description |
|---|---|
listTasks(options) |
List tasks with filtering, pagination, and search |
getTask(id) |
Get a single task with metadata |
createTask(data) |
Create a new task |
updateTask(id, data) |
Update task fields |
deleteTask(id) |
Delete a task |
batchUpdateTasks(updates) |
Update multiple tasks in a transaction |
getTasksByProject(projectId, options) |
List tasks for a project |
getTasksByStatus(status, options) |
List tasks by status |
getTasksByOwner(owner, options) |
List tasks assigned to an owner |
getTaskGraph(projectId) |
Get task dependency graph for a project |
Task options:
project_id— Filter by projectstatus— Filter by statuspriority— Filter by priorityowner— Filter by ownerlabels— Filter by labelssearch— Full-text search in title and descriptionsort— Sort field (newest,oldest,updated,alpha,priority,due_date)limit/offset— Paginationupdated_since— Incremental sync (only tasks modified after timestamp)
| Method | Description |
|---|---|
listWorkflows(options) |
List all workflows |
getWorkflow(id) |
Get a workflow by ID |
createWorkflow(data) |
Create a new workflow |
updateWorkflow(id, data) |
Update a workflow |
deleteWorkflow(id) |
Delete a workflow |
getDefaultWorkflow(projectId) |
Get default workflow for a project |
| Method | Description |
|---|---|
logAudit(taskId, actor, action, oldValue, newValue) |
Create an audit log entry |
getAuditLog(options) |
Query audit log with filters |
getAuditForTask(taskId) |
Get all audit entries for a task |
Audit log options:
task_id— Filter by taskactor— Filter by actoraction— Filter by action typefrom/to— Date range filterlimit/offset— Pagination
const DEFAULT_WORKFLOW_STATES = [
'backlog',
'ready',
'in_progress',
'blocked',
'review',
'completed',
'archived'
];const PRIORITIES = ['low', 'medium', 'high', 'critical'];const PROJECT_STATUSES = ['active', 'paused', 'archived'];The storage layer automatically handles dependency-based blocking:
- When a task has
dependency_idspointing to incomplete tasks, its status may be auto-blocked - When all dependencies complete, blocked tasks can be auto-unblocked
- Dependency block events are recorded in the audit log with action
auto_block_unmet_dependencies
Creates a structured audit entry for dependency-related status changes.
The storage layer includes fixture project detection to filter out test data:
const FIXTURE_PROJECT_PATTERNS = [
/^board test project\b/i,
/^test project\b/i,
/^test$/i,
/^my new project\b/i,
/\bfixture\b/i,
/\bseed\b/i
];Projects matching these patterns are excluded from normal listings unless explicitly requested.
The storage layer integrates with lib/qmd-security for input validation and sanitization. Key protections:
- SQL injection: All queries use parameterized statements via
pgPool - Input normalization: Metadata fields are validated as plain objects (no arrays)
- Project ID normalization: Empty or "all" project IDs are normalized to empty string (list all)
- Pagination limits: Maximum page size of 200 enforced
The storage layer imports bootstrap data from org-bootstrap.js:
- Departments:
DEPARTMENTS— Seed department definitions - Agent profiles:
AGENT_PROFILES— Seed agent profile definitions - Lookup helpers:
getDepartmentBySlug(),getAgentProfileById()
These are used as fallbacks when the database tables haven't been seeded yet (before migrations are applied).
All storage methods return Promises and throw on database errors. The caller (task-server API layer) is responsible for:
- Catching errors
- Mapping to HTTP status codes (404, 500, etc.)
- Returning error responses to the client
Common patterns:
SELECT ... WHERE id = $1returning no rows → 404 Not FoundINSERT ... ON CONFLICT→ 409 Conflict (duplicate)- Connection errors → 503 Service Unavailable
- Connection pooling: Uses
pg.Poolwith default pool size (10 connections) - Prepared statements: All queries use parameterized
$1, $2, ...placeholders - GIN indexes: Array fields (
labels,dependency_ids) and JSONB (metadata) have GIN indexes - Composite indexes: Common query patterns (project+status, owner, due_date) have dedicated indexes
- Pagination: Always use
limit/offsetfor large result sets