firestore data model
this page maps the Firestore collections that owlette currently reads or writes, split by access path.
- client/rules-visible paths are covered by
firestore.rulesand can be read or written by browser or agent Firebase clients when the rule allows it. - server-only Admin SDK paths are intentionally denied by rules, or fall through to the deny-all rule. Route handlers, Cloud Functions, and server workers use the Admin SDK for these paths.
- legacy paths still exist for older dashboard, agent, or Cortex flows but are not the model new docs should teach first.
- current public API paths are the storage models behind public API routes. API routes enforce auth and scope before touching Firestore.
client/rules-visible
These paths are explicitly matched in firestore.rules.
firestore/
|-- sites/{siteId}
| |-- machines/{machineId}
| | |-- commands/pending
| | |-- commands/completed
| | |-- screenshots/{screenshotId}
| | |-- installed_software/{softwareId}
| | |-- hardware/{docId}
| | `-- metrics_history/{bucketId}
| |-- deployments/{deploymentId}
| |-- installer_templates/{templateId}
| |-- project_templates/{templateId}
| |-- project_distributions/{distributionId}
| |-- roosts/{roostId}
| | |-- versions/{versionId}
| | `-- target_state/{machineId}
| |-- webhooks/{webhookId}
| |-- logs/{logId}
| |-- audit_log/{entryId}
| `-- settings/{settingId}
|-- config/{siteId}
| |-- machines/{machineId}
| |-- schedule_presets/{presetId}
| |-- reboot_presets/{presetId}
| `-- project_distribution_presets/{presetId}
|-- users/{userId}
| |-- api_keys/{keyId}
| |-- settings/{settingId}
| `-- devicePrefs/{docId}
|-- installer_metadata/{document=**}
|-- system_presets/{presetId}
|-- api_keys/{keyHash}
|-- agent_tokens/{tokenId}
|-- agent_refresh_tokens/{tokenHash}
|-- device_codes/{phrase}
`-- chats/{chatId}
`-- messages/{messageId}sites/{siteId}
Top-level site document. Site reads require canAccessSite(siteId). Direct
create, update, and delete are service-account only.
| field | type | notes |
|---|---|---|
name | string | Site display name. |
owner | string | UID of the user who created or owns the site. |
createdAt | timestamp | Site creation time. |
timezone | string | IANA timezone used by site-scoped scheduling surfaces. |
tier / plan | string | Pricing or quota tier, depending on caller generation. |
roostEnabled | boolean | Site-level roost kill switch. Missing or true means enabled. |
sites/{siteId}/machines/{machineId}
Machine presence, status, metrics, process status, reboot state, live-view
state, and capability flags live on the machine document itself. There are no
current presence or status child documents.
Agents can read and write only their own machine document. Site members can read machine documents for sites they can access. Control-plane writes from the web go through API routes.
| field | type | notes |
|---|---|---|
machineId | string | Hostname / machine identifier. |
siteId | string | Owning site ID. |
online | boolean | Agent-reported online flag. The dashboard also checks heartbeat age. |
lastHeartbeat | timestamp | Updated with each heartbeat/metrics write. |
agent_version | string | Agent build version. |
machine_timezone | string | Legacy Windows timezone label. |
machine_timezone_iana | string | IANA timezone reported by newer agents. |
cortexEnabled | boolean | Per-machine Cortex delivery kill switch. Missing means enabled. |
cortexStatus | map | Local Cortex status and heartbeat fields. |
rebooting, shuttingDown | boolean | Current machine power-operation flags. |
rebootScheduledAt, shutdownScheduledAt | timestamp/number | Countdown anchors used by the dashboard. |
rebootPending | map | { active, processName, reason, timestamp }. |
rebootState | map | Last fired schedule entry plus current reboot attempt. |
lastScreenshot | map/null | Latest screenshot pointer surfaced by live view. |
liveView | map | { active, interval, startedAt, expiresAt } when live view is running. |
capabilities | map | Feature gates such as displayRemoteApply. |
metrics | map | Current metrics payload described below. |
Current metrics use schema version 2 and are nested on the machine document:
| field | type | notes |
|---|---|---|
metrics.schemaVersion | number | Current value is 2. |
metrics.profileHash | string/null | Matches hardware/profile.signatureHash. |
metrics.timestamp | timestamp | Metrics write time. |
metrics.cpus | map | CPU metrics keyed by profile ID. |
metrics.memory | map | Memory percent and used GB. |
metrics.disks | map | Disk usage keyed by profile ID. |
metrics.diskio | map | Per-volume IO metrics. |
metrics.gpus | map | GPU metrics keyed by profile ID. |
metrics.nics | map | Network interface throughput keyed by profile ID. |
metrics.network | map | Ping, packet loss, gateway, and network health fields. |
metrics.primary | map | Current primary CPU/disk/GPU/NIC IDs. |
metrics.processes | map | Runtime process status map from the agent. |
metrics.displayDriftCount | number | Count of monitors that drift from assigned topology. |
Legacy singular metrics.cpu, metrics.disk, and metrics.gpu are deleted
by current agents after writing v2 metrics. The dashboard still shims older
cached shapes during rollout windows.
sites/{siteId}/machines/{machineId}/commands/{commandDoc}
Command queues use two singleton documents:
commands/pendingcommands/completed
Each document stores command IDs as top-level map fields. A pending document looks like this:
{
"restart_DESKTOP01_1712000000000": {
"type": "restart_process",
"status": "pending",
"createdAt": "<server timestamp>",
"expiresAt": "<timestamp>",
"auditCorrelationId": "optional",
"process_name": "TouchDesigner"
}
}Completed, failed, cancelled, and in-progress states are written into
commands/completed under the same command ID:
| field | type | notes |
|---|---|---|
status | string | completed, failed, cancelled, or an intermediate status such as downloading. |
result | any | Present for successful or cancelled terminal states. |
error | string | Present for failed terminal states. |
completedAt | timestamp | Terminal completion time. |
updatedAt | timestamp | Progress update time. |
progress | number | Optional progress percentage. |
deployment_id | string | Optional deployment correlation. |
type | string | Original command type when supplied by the handler. |
The server writes pending commands with writeCommandFanOut(). The agent
listens to commands/pending, processes unseen map entries, writes the result
to commands/completed, then deletes the command field from commands/pending.
sites/{siteId}/machines/{machineId}/hardware/{docId}
Hardware profile documents are readable by site members and writable by the agent for its own machine.
| doc ID | writer | notes |
|---|---|---|
profile | agent | Static CPU, disk, GPU, and NIC inventory. |
display | agent | Live display topology and assignment/drift data. |
displayModes | agent | On-demand display mode catalogue. |
hardware/profile uses this shape:
| field | type | notes |
|---|---|---|
schemaVersion | number | Hardware profile schema. |
signatureHash | string | Stable hash used by metrics joins. |
capturedAt | timestamp | Capture time. |
agentVersion | string | Agent version that captured the profile. |
cpus, disks, gpus, nics | array | Static device profile arrays. |
sites/{siteId}/machines/{machineId}/screenshots/{screenshotId}
Screenshot history is written by server APIs after upload. Site members can read the history gallery.
| field | type | notes |
|---|---|---|
url | string | Storage URL for the image. |
timestamp | timestamp/number | Capture time. |
sizeKB | number | Image size in KB. |
storagePath | string | Backing object path when present. |
sites/{siteId}/machines/{machineId}/installed_software/{softwareId}
Agent-written software inventory from the Windows registry.
| field | type | notes |
|---|---|---|
name, version, publisher | string | Registry metadata. |
install_location | string | Installation directory. |
uninstall_command | string | Uninstall command from registry. |
installer_type | string | Detected installer family. |
registry_key | string | Registry key reference. |
detected_at | timestamp | Inventory write time. |
sites/{siteId}/machines/{machineId}/metrics_history/{bucketId}
Daily metric history buckets, keyed by YYYY-MM-DD, are written by Cloud
Functions from machine metric updates.
| field | type | notes |
|---|---|---|
samples | array | Time-series samples. |
meta.lastSample | timestamp | Last sample time. |
meta.sampleCount | number | Number of samples in the bucket. |
meta.resolution | string | Aggregation resolution. |
sites/{siteId}/deployments/{deploymentId}
Installer deployment records. Site members can read them. Server APIs create, update, cancel, retry, uninstall, and delete them.
| field | type | notes |
|---|---|---|
name | string | Deployment display name. |
installer_name, installer_url | string | Installer metadata. |
silent_flags | string | Optional installer flags. |
verify_path | string/null | Optional post-install check path. |
close_processes | array | Processes to close before install. |
parallel_install | boolean | Whether targets can install in parallel. |
targets | array | { machineId, status, progress, error? } rows. |
status | string | pending, in_progress, completed, failed, partial, cancelled, or uninstalled. |
createdBy, createdAt, updatedAt | string/timestamp | Audit metadata. |
Target status values include pending, closing_processes, downloading,
installing, completed, failed, cancelled, and uninstalled.
sites/{siteId}/installer_templates/{templateId}
Site-scoped deployment templates managed through server APIs and dashboard actions.
| field | type | notes |
|---|---|---|
name | string | Template display name. |
installer_name, installer_url | string | Installer source. |
silent_flags | string | Suggested installer flags. |
verify_path | string/null | Optional verification path. |
close_processes | array | Process names to close before install. |
timeout_seconds | number/null | Optional install timeout. |
createdAt, updatedAt | timestamp | Timestamps. |
sites/{siteId}/roosts/{roostId}
The v2 project distribution model. Site members can create roost shells and edit non-pointer metadata. Version pointer changes are server-mediated through API routes.
| field | type | notes |
|---|---|---|
schemaVersion | number | Current roost schema is 2. |
name | string | Display name. |
targets | string[] | Target machine IDs. |
extractPath | string | Destination root on target machines. |
versionCounter | number | Monotonic per-roost version number counter. |
currentVersionId | string/null | Current immutable version ID. |
currentVersionNumber | number/null | Denormalized current version number. |
currentVersionDescription | string/null | Denormalized current version description. |
previousVersionId | string/null | Previous head before the last publish/rollback. |
versionUrl | string/null | Unsigned R2 object URL for the current version body. |
totalFiles, totalSize | number | Current version summary. |
createdAt, createdBy, updatedAt | timestamp/string | Audit metadata. |
Direct Firestore create requires name, targets, createdAt, and
schemaVersion: 2, and forbids client-supplied version pointer fields. Direct
update cannot change pointer fields or schemaVersion.
sites/{siteId}/roosts/{roostId}/versions/{versionId}
Immutable version metadata. Version bodies live in R2, not in this document. Client-side create, update, and delete are denied by rules.
| field | type | notes |
|---|---|---|
versionId | string | Content-addressed SHA-256 of the canonical version body. |
versionNumber | number | Monotonic, 1-indexed number within the roost. |
description | string | Publish description. |
versionUrl | string | R2 object URL for the version body. |
createdAt, createdBy | timestamp/string | Author metadata. |
totalSize, totalFiles | number | Version summary. |
parentVersionId | string/null | Head version before this publish. |
sites/{siteId}/roosts/{roostId}/target_state/{machineId}
Agent-reported per-target reality for roost sync. The agent for the specific machine can create or update its own document; site members can read or delete stale documents.
| field | type | notes |
|---|---|---|
reportedVersionId | string | Version the agent is syncing or has committed. |
status | string | pending, downloading, assembling, committed, failed, or cancelled. |
updatedAt | timestamp | Last report time. |
error | string | Truncated error message on failure. |
chunks_fetched, chunks_total, chunks_dedup | number | Download progress counters. |
files_total, files_assembled, files_skipped | number | Assembly progress counters. |
sites/{siteId}/logs/{logId}
Site-level event logs. This is the current dashboard log collection. It is not under each machine document.
| field | type | notes |
|---|---|---|
timestamp | timestamp | Event time. |
action | string | Event type, such as deployment or process action. |
level | string | info, warning, or error. |
machineId, machineName | string | Source machine. |
processName | string | Optional process. |
processId | string | Optional process id. |
details | map/string | Optional details. |
userId | string | Optional initiating user. |
screenshotUrl | string | Optional screenshot pointer. |
eventType | string | Optional agent alert event type. |
exePath, suggestedPaths | string/array | Present on exe_missing entries when the agent found missing executable context. |
sites/{siteId}/audit_log/{entryId}
Security-boundary audit records. Site admins can read. Direct client writes are denied; server handlers write through audit helpers.
sites/{siteId}/webhooks/{webhookId}
Site-scoped webhook configuration. Site members can read. All writes go through server APIs.
| field | type | notes |
|---|---|---|
url, name | string | Delivery endpoint and label. |
events | string[] | Subscribed webhook events. |
enabled | boolean | Delivery enabled flag. |
secret | string | HMAC secret stored server-side. |
createdAt, createdBy, updatedAt | timestamp/string | Metadata. |
lastTriggered, lastStatus, failCount | timestamp/number | Delivery health fields. |
Delivery history is stored under webhook subcollections by server APIs and is not directly covered by a client rules stanza.
sites/{siteId}/settings/{settingId}
Site settings such as shared LLM/Cortex configuration. Site members can read; server APIs write.
Known document IDs include:
llmcortex
config/{siteId}/machines/{machineId}
Process configuration and machine-level persistent settings. Agents can read and write their own config; server APIs also write config changes.
| field | type | notes |
|---|---|---|
version | string | Config schema version. |
processes | array | Process config objects. |
rebootSchedule | map | Offline-capable scheduled reboot config. |
displays | map | Display assignment and auto-restore settings. |
environment, sentry, watchdog | map | User-editable top-level config sections. |
Current process config uses id/processId, name, exe_path, file_path,
cwd, priority, visibility, time_delay, time_to_init,
relaunch_attempts, launch_mode, derived autolaunch, schedules, and
optional schedulePresetId.
config preset collections
| path | purpose | direct access |
|---|---|---|
config/{siteId}/schedule_presets/{presetId} | Process schedule presets. | Site members read; server writes. |
config/{siteId}/reboot_presets/{presetId} | Reboot schedule presets. | Site members read; server writes. |
config/{siteId}/project_distribution_presets/{presetId} | Legacy distribution presets. | Site members read; server writes. |
users/{userId}
User profile and access metadata. Users can read their own document;
superadmins can read every user. Users can self-create only as member.
Server APIs manage roles, site assignments, and deletion.
| field | type | notes |
|---|---|---|
email | string | User email. |
displayName | string | Optional display name. |
role | string | member, admin, or superadmin. |
sites | string[] | Site IDs assigned to members/admins. |
createdAt | timestamp | Creation time. |
mfaEnrolled, mfaSecret, backupCodes | boolean/string/array | MFA state. |
passkeyEnrolled | boolean | Whether passkeys exist. |
user subcollections
| path | purpose | direct access |
|---|---|---|
users/{userId}/api_keys/{keyId} | User-visible API key inventory metadata. | User can read own keys; direct writes denied. |
users/{userId}/settings/{settingId} | User preferences and encrypted LLM settings. | User can read/write own settings. |
users/{userId}/devicePrefs/{docId} | Per-device UI preferences. | User can read/write own device preferences. |
The raw API key hash lookup lives at top-level api_keys/{keyHash} and is
server-only.
installer_metadata/{document=**}
Publicly readable installer metadata. Server APIs write latest and version records.
Common documents:
installer_metadata/latestinstaller_metadata/data/versions/{version}
system_presets/{presetId}
Platform-level deployment preset library. Authenticated users can read. Server APIs create, update, and delete.
| field | type | notes |
|---|---|---|
name, software_name, category, description | string | Display metadata. |
installer_name, installer_url | string | Installer source. |
silent_flags | string | Suggested silent flags. |
verify_path | string/null | Optional install verification path. |
close_processes | string[] | Processes to close before installing. |
timeout_seconds | number/null | Optional timeout. |
order | number | Sort order. |
createdAt, updatedAt | timestamp | Timestamps. |
server-only Admin SDK
These collections are written or read through server routes, Cloud Functions, or server workers. Direct browser and agent access is denied by explicit rules or by the catch-all deny rule.
| path | purpose |
|---|---|
api_keys/{keyHash} | Fast API-key lookup table with key hash, user, key ID, scopes, expiry, and revocation metadata. |
agent_tokens/{tokenId} | Legacy agent registration token state. |
agent_refresh_tokens/{tokenHash} | Hashed agent refresh tokens with site and machine binding. |
device_codes/{phrase} | 10-minute device-code pairing phrase state. Deleted after agent poll/consume or expiry. |
mfa_pending/{userId} | Temporary MFA setup secret and expiry. |
webauthn_challenges/{challengeId} | Single-use passkey registration/authentication challenge. |
users/{userId}/passkeys/{credentialId} | Passkey credentials and metadata, managed by passkey APIs. |
installer_uploads/{uploadId} | Temporary installer upload sessions. |
bug_reports/{reportId} | User/agent feedback and bug submissions. |
global/security_config | Platform security kill-switch config. |
global/audit_log/entries/{entryId} | Platform audit records for non-site-scoped actions. |
siteChunks/{digest} | Emulator/test chunk-presence rows used when R2 is not available. |
siteChunks/{digest}
Production chunk bytes live in Cloudflare R2. In emulator/E2E mode,
hasChunk() checks this top-level collection instead.
| field | type | notes |
|---|---|---|
siteId | string | Site that owns the seeded chunk. |
hash | string | Bare 64-hex SHA-256 digest. |
size | number | Seeded size in bytes. |
createdAt | timestamp | Seed time. |
legacy
These paths may still be present during migration windows. New integrations should use the current public API storage models and routes instead.
sites/{siteId}/project_distributions/{distributionId}
Legacy project distribution records. They coexist with roost v2 during the cutover but are not the current project sync model.
| field | type | notes |
|---|---|---|
name | string | Distribution name. |
project_name, project_url | string | Legacy package metadata. |
extract_path | string | Destination path. |
targets | array | Per-machine target rows. |
status | string | pending, in_progress, completed, failed, partial, or cancelled. |
createdAt, createdBy, updatedAt | timestamp/string | Metadata. |
sites/{siteId}/project_templates/{templateId}
Legacy reusable project distribution templates. Current roost upload/publish flows do not depend on this path.
config/{siteId}/project_distribution_presets/{presetId}
Legacy distribution preset library. It remains rules-visible for older UI flows and built-in preset overrides.
chats/{chatId} and chats/{chatId}/messages/{messageId}
Legacy Cortex conversation storage. Rules still expose this collection to the owning user, and older Cortex categorization paths still read/write it.
| field | type | notes |
|---|---|---|
userId | string | Conversation owner. |
siteId | string | Site context. |
targetType | string | machine or site. |
targetMachineId, machineName | string/null | Target context. |
title, category | string | Conversation label and LLM category. |
source | string | user or autonomous. |
eventId, autonomousSummary | string/null | Autonomous event linkage. |
createdAt, updatedAt | timestamp | Timestamps. |
Messages under messages/{messageId} contain role, content, and
createdAt.
legacy/local Cortex bridge paths
Older local Cortex bridge code references these paths:
sites/{siteId}/machines/{machineId}/cortex/active-chatsites/{siteId}/cortex-events/{eventId}sites/{siteId}/cortex-state/lock
They are not the current public chat API storage model. Treat them as legacy dashboard/agent internals unless the owning Cortex code is explicitly being updated.
machine-level log shipping
The older log-shipping helper attempts to write
sites/{siteId}/machines/{machineId}/logs/{logId}. Current rules do not
define that subcollection, and current dashboard activity logs use
sites/{siteId}/logs/{logId}.
current public API
These storage models sit behind public API routes. Clients should call the API surface rather than reading or writing these collections directly.
public chat storage
Current public Cortex chat APIs use chat_conversations/{conversationId}.
The most recent messages are embedded on the conversation document; older
messages spill to chat_conversations/{conversationId}/chat_messages/{messageId}
after the embedded array reaches 200 entries.
| field | type | notes |
|---|---|---|
conversationId | string | conv_ plus random URL-safe ID. |
title | string | Normalized title, max 100 chars. |
siteId | string | Site context. |
machineId | string | Optional target machine. |
ownerUid | string | Conversation owner. |
createdAt, updatedAt | timestamp | Timestamps. |
deletedAt | timestamp | Soft-delete marker when present. |
messages | array | Embedded most-recent messages. |
messageCount | number | Lifetime message count, including spilled rows. |
Embedded and spilled message rows contain:
| field | type | notes |
|---|---|---|
role | string | user, assistant, or system. |
content | string | Message text. |
timestamp | timestamp | Message write time. |
spilledAt | timestamp | Present only on spilled rows. |
roost quota and usage
Quota and usage storage is server-managed. Public routes such as
GET /api/sites/{siteId}/quota and
GET /api/sites/{siteId}/quota/history expose scoped views.
| path | writer/reader | purpose |
|---|---|---|
sites/{siteId}/roost/quota | Cloud Functions and quota API | Current storage tier, used bytes, plan limit, alarm state, and reconciliation time. |
sites/{siteId}/roost/quota/pending/{reservationId} | quota pre-upload check | Temporary pending byte reservations. |
sites/{siteId}/quota_alarms/{alarmId} | quota reconciler | Alarm threshold crossings with threshold and firedAt. |
sites/{siteId}/usage_events/{eventId} | telemetry function | Raw usage events: class_a_op, class_b_op, egress, or storage_snapshot. |
sites/{siteId}/usage_summaries/{yyyyMm} | telemetry aggregator | Month-to-date counters and cost rollups. |
roost version bodies and chunk bytes
Firestore stores roost metadata and pointers only. Actual version bodies and chunk bytes live in R2:
| object | storage | key shape |
|---|---|---|
| chunks | R2 content bucket | project-content/{siteId}/{firstTwoHex}/{digest} |
| version bodies | R2 manifests bucket | project-manifests/{siteId}/{roostId}/{versionId}.json |
The Firestore roosts/{roostId}/versions/{versionId} document records
metadata and the R2 URL; it does not contain the full version body.
cortex tools reference
Complete reference for all 46 public web Cortex tools from web/lib/mcp-tools.ts, organized by tier.
architecture
owlette uses a split control, state, and data plane. Cloud Firestore is the real-time state store and command queue, Next.js API routes handle authenticated control actions, and object storage carries large binaries plus roost chunks and version bodies. Agents and the dashboard do not maintain a direct persistent connection to each other; they coordinate through API routes, Firestore state, and signed storage URLs.