Workspaces and threads¶
A workspace is the top-level local boundary for conversations, allowed filesystem roots, retrieval indexes, default models, built-in tool policy, and approval policy. A thread belongs to exactly one workspace and stores an ordered conversation plus optional model and tool overrides.
Network
└── Workspace
├── configured filesystem roots
├── one .subconscious/sidecar.db per indexed root
└── Thread
└── Message (user, assistant, system, or tool)
Workspace creation uses whichever existing network the database returns when one is present; that selection is not ordered. If no network exists, the engine creates one named default. Networks are not exposed by the current API. Although the database has a default_workspace_uuid field, the API does not use it as an active selection.
Selection belongs to each client¶
The engine has no server-wide “current workspace” or “current thread.” Clients list resources, store their own selected UUIDs, and send those UUIDs on every operation. This allows desktop, VS Code, terminal, and browser clients to use different selections while sharing the same underlying data.
Workspace fields and roots¶
name is the only required create/update field. description is display metadata. defaultModelId is the model fallback for new or unconfigured threads. The remaining policy fields are nullable strings containing JSON:
| Field | Meaning |
|---|---|
directories |
Array of absolute root paths available to scoped file APIs and RAG |
toolsConfig |
Baseline built-in tool group/tool enablement |
approvalConfig |
Baseline create/read/update/delete approval requirements |
ragConfig |
Per-root retrieval enablement |
Workspace CRUD does not validate the JSON or filesystem paths. File/RAG use later requires each selected root to exist, be absolute, and not be a symlink, junction, or reparse point. Root indexes are zero-based positions in the directories array; changing that order changes what a stored rootIndex identifies.
skills_config also exists in persistence and is consumed during graph execution, but it is not exposed by the current workspace REST DTO.
Creating and updating workspaces¶
POST /workspaces creates a UUID and timestamps. PUT /workspaces/{uuid} uses the same schema and is a full replacement: name is required and every omitted optional field is stored as null. Read the current workspace, merge client edits, then send the complete desired object.
Deleting a workspace cascades its threads, messages, and workspace-owned todo/memory/note/contact database records. It does not delete configured folders or .subconscious/sidecar.db files written inside those roots.
Threads¶
Threads store title, description, model default, tool override, timestamps, and messages. POST /threads requires workspaceUuid. PUT /threads/{uuid} is patch-like: omitted fields are unchanged, explicit null clears a field, and even {} updates updatedAt.
The persisted thread also has approval_config and skills_config, but current thread REST schemas do not expose either. Thread approval therefore normally inherits the workspace policy, while internal/persisted values may still be consumed by chat.
Deleting a thread cascades its messages. Workspace-owned todo/memory references to that thread become null rather than deleting those records.
Model inheritance¶
For each WebSocket chat turn, model resolution is:
chat.send.data.model_idthread.defaultModelIdworkspace.defaultModelId- built-in
echo
The engine first resolves an exact model configuration ID, then a matching stored model name. After resolution, it writes the configuration ID to the thread. Consequently, a one-turn model_id override also becomes that thread’s default for later turns.
Tool configuration inheritance¶
The workspace toolsConfig JSON object is the baseline. A non-empty thread toolsConfig object is recursively deep-merged over it: nested objects merge, while thread scalars, arrays, and null replace workspace values. A null or empty thread value inherits the workspace; {} also leaves the baseline intact.
{
"builtin_enabled": true,
"builtin": {
"filesystem": {
"enabled": true,
"tools": {
"delete_filesystem_path": false
}
}
}
}
builtin_enabled:falsedisables every built-in model tool.builtin.<group>.enabled:falsedisables a group.builtin.<group>.tools.<name>:falsedisables one tool.chat.send.tools_enabled:falsedisables built-ins for that turn, but not client-registered UI tools.
Malformed or non-object stored JSON is accepted by workspace/thread CRUD but fails when chat parses it. The HTTP configured-tool registry and the built-in runtime registry are separate in the current execution path; a configured record is not automatically executable by chat.
Approval inheritance¶
The engine uses a persisted thread approval configuration when present; otherwise it uses workspace.approvalConfig. The current thread REST contract does not expose approval configuration, so normal public-API clients configure approvals at workspace level.
{"create":true,"read":false,"update":true,"delete":true}
Missing, malformed, or non-object approval JSON defaults every operation to approval-required. Legacy query and mutation keys are accepted. Tools are classified by explicit name/prefix; unknown names default to update. Registered UI tools always require approval.
Generic filesystem tools are not workspace-scoped
Workspace file APIs, context-file tools, and RAG stay inside configured roots. The built-in filesystem model-tool group can intentionally list, read, write, create, delete, move, or copy any path available to the engine OS account. Approval policy is its engine-level safety boundary.
Starting or continuing a chat¶
A WebSocket chat.send targets exactly one existing thread_uuid or a workspace_uuid for implicit thread creation. For a workspace target, the title is normalized from the first message, truncated to 60 characters, or set to New conversation when blank.
The engine resolves the model before the first commit. If the model does not exist, an implicit thread is rolled back. Once model resolution succeeds, it commits the thread and user message before running the graph/model. Cancellation, model failure, approval interruption, or client-tool failure can therefore leave a user message with no assistant response. Clients should render this as an interrupted/failed turn rather than assuming alternating pairs.
Only one chat turn runs on each WebSocket connection, but two clients can run turns against the same thread concurrently. There is no thread-level lock, so consumers should order refreshed messages using server timestamps and should tolerate interleaving.
Messages and history¶
Messages have one of four roles:
| Role | Produced by | Included in later model history |
|---|---|---|
user |
WebSocket chat or REST insertion | Yes |
assistant |
Completed agent turn or REST insertion | Yes |
system |
WebSocket notification or REST insertion | Yes |
tool |
Compact JSON execution record or REST insertion | No |
WebSocket history is ordered by (created_at,id) before it is passed to the model. GET /threads/{uuid}/messages orders by createdAt only, so messages with identical timestamps do not have a documented secondary order. REST message insertion and WebSocket user, system, and assistant persistence update the thread’s updatedAt; WebSocket tool-record insertion does not independently update it. Thread lists therefore usually behave like recent-conversation lists.
REST POST /messages stores the supplied role/content directly and does not run an agent. WebSocket chat.notification stores a system message without running an agent. Chat stores user, zero or more tool, and assistant messages over the lifecycle of a successful turn.
Files, context, and retrieval¶
Workspace roots define both scoped file access and RAG placement. Each indexed root owns its own .subconscious/sidecar.db. Removing a root from a workspace or deleting the workspace does not delete that sidecar. ragConfig can disable indexing/search for a configured path without removing the path from file access.
Active and tagged chat files carry a root index plus relative path. Attachments carry bytes independently of workspace roots. Their metadata is added to the prompt; supported binary media can also be sent directly to the model. File references are resolved only when tools use them, so stale paths can be accepted in the initial frame and fail later.
Cross-client behavior¶
All clients share database resources, but each maintains its own selected UUIDs. SSE subscribers see future thread/message/graph events. Hello-registered WebSocket sessions receive only selected WebSocket-originated thread/message broadcasts, not a durable or complete change stream. Refresh lists after reconnect and after operations without a corresponding event.
Workspace deletion cascades database children but creates no delete event. Thread deletion cascades messages and nulls thread references held by workspace todo/memory records. Neither deletion modifies user files.