Protocol Architecture
How the Data, System, and UI protocols work together as one cohesive system
The Protocol Stack
The architecture is built on foundational protocols that work together as a unified system:
Data Protocol
ObjectQL: Structure, queries, and constraints.
UI Protocol
ObjectUI: Presentation, interaction, and routing.
System Protocol
ObjectOS: Control, runtime, and governance.
Automation Protocol
Business Logic: Flow, Workflow, Triggers.
AI Protocol
Intelligence: Agents, RAG, Models.
Hub Protocol
Management: Multi-tenancy, Marketplace, Licensing.
Why Separated Layers?
Traditional applications tightly couple data, business logic, and presentation. This creates Implementation Coupling — changing one layer forces changes across the entire stack.
ObjectStack enforces Separation of Concerns through protocol boundaries:
┌───────────────────────────────────────────────────────────┐
│ UI Protocol & AI Protocol │
│ Apps, Views, Dashboards, Agents, RAG │
│ "How do users and agents interact?" │
└───────────────────────────┬───────────────────────────────┘
│ Interface
┌───────────────────────────┴───────────────────────────────┐
│ System Protocol & Automation Protocol & Hub Protocol │
│ Auth, Permissions, Orchestration, Multi-tenancy │
│ "Who/What can do what, when, and where?" │
└───────────────────────────┬───────────────────────────────┘
│ Control
┌───────────────────────────┴───────────────────────────────┐
│ Data Protocol │
│ Objects, Fields, Queries, Drivers │
│ "What is the data structure?" │
└───────────────────────────────────────────────────────────┘Layer 1: ObjectQL (Data Protocol)
Role: Define the Structure and Intent of data.
Responsibilities:
- Object schema definitions (what is a "Customer"?)
- Field types and validation rules
- Query language (filtering, sorting, aggregation)
- Database drivers (Postgres, MongoDB, SQLite)
Key Principle: ObjectQL knows nothing about users, permissions, or UI. It only cares about data structure and queries.
Example: Defining a Customer Object
// packages/crm/src/objects/customer.object.ts
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Customer = ObjectSchema.create({
name: 'customer',
label: 'Customer',
icon: 'building',
fields: {
name: Field.text({
label: 'Company Name',
required: true,
maxLength: 120,
}),
industry: Field.select({
label: 'Industry',
options: [
{ label: 'Technology', value: 'technology' },
{ label: 'Finance', value: 'finance' },
{ label: 'Healthcare', value: 'healthcare' },
{ label: 'Retail', value: 'retail' },
],
}),
annual_revenue: Field.currency({
label: 'Annual Revenue',
scale: 2,
}),
primary_contact: Field.lookup('contact', {
label: 'Primary Contact',
}),
},
});This definition is pure metadata. It doesn't know:
- Who can see this data
- How to render a form
- When to trigger workflows
That's the job of the other layers.
Layer 2: ObjectOS (Control Protocol)
Role: Manage the Lifecycle and Governance of requests.
Responsibilities:
- Authentication (who is this user?)
- Authorization (can they access this field?)
- Workflows and automations (what happens after save?)
- Event processing (audit logs, notifications)
- Multi-tenancy and data isolation
Key Principle: ObjectOS acts as the Gateway. No layer can directly access the database; all requests must pass through the OS Kernel.
Example: Permission Rules
// packages/crm/src/permissions/customer.permission.ts
import { Permission } from '@objectstack/spec';
export const CustomerPermission = Permission({
object: 'customer',
rules: [
{
profile: 'sales_rep',
crud: {
create: true,
read: true,
update: true,
delete: false, // Only managers can delete
},
fieldPermissions: {
annual_revenue: { read: true, edit: false }, // Read-only
},
},
{
profile: 'sales_manager',
crud: {
create: true,
read: true,
update: true,
delete: true,
},
},
],
});Example: Workflow Automation
// packages/crm/src/workflows/customer.workflow.ts
import { Workflow } from '@objectstack/spec';
export const CustomerWorkflow = Workflow({
object: 'customer',
trigger: 'after_create',
conditions: [
{ field: 'annual_revenue', operator: 'greaterThan', value: 1000000 },
],
actions: [
{
type: 'assign_owner',
params: { owner: 'enterprise_sales_team' },
},
{
type: 'send_email',
params: {
template: 'high_value_customer_alert',
to: 'sales-leadership@company.com',
},
},
],
});ObjectOS orchestrates these rules at runtime, independent of the data structure or UI.
Layer 3: ObjectUI (View Protocol)
Role: Render the Presentation and handle User Interaction.
Responsibilities:
- App navigation and branding
- List views (grid, kanban, calendar)
- Form layouts (simple, tabbed, wizard)
- Dashboards and reports
- Actions and buttons
Key Principle: ObjectUI is a Rendering Engine, not a hardcoded interface. It asks ObjectQL "What is the schema?" and dynamically generates the UI.
Example: List View
// packages/crm/src/views/customer_list.view.ts
import { ListView } from '@objectstack/spec';
export const CustomerListView = ListView({
object: 'customer',
label: 'All Customers',
type: 'grid',
columns: [
{ field: 'name', width: 200 },
{ field: 'industry', width: 150 },
{ field: 'annual_revenue', width: 150 },
{ field: 'primary_contact', width: 180 },
],
filters: [
{ field: 'industry', operator: 'equals' },
{ field: 'annual_revenue', operator: 'greaterThan' },
],
defaultSort: { field: 'name', direction: 'asc' },
});Example: Form View
// packages/crm/src/views/customer_form.view.ts
import { FormView } from '@objectstack/spec';
export const CustomerFormView = FormView({
object: 'customer',
label: 'Customer Details',
type: 'tabbed',
tabs: [
{
label: 'Overview',
sections: [
{
label: 'Company Information',
fields: ['name', 'industry', 'annual_revenue'],
},
{
label: 'Contact',
fields: ['primary_contact'],
},
],
},
{
label: 'Related Records',
sections: [
{
label: 'Opportunities',
component: 'related_list',
object: 'opportunity',
filter: { customer: '$recordId' },
},
],
},
],
});The UI doesn't "know" the field types. It asks ObjectQL for the schema and renders accordingly:
Field.text→ Text inputField.select→ DropdownField.lookup→ Autocomplete lookupField.currency→ Number input with currency formatting
How They Work Together
Let's trace a real-world scenario: A sales rep creates a new high-value customer.
Step 1: User Action (ObjectUI)
User fills out the "Create Customer" form:
- Name: "Acme Corp"
- Industry: "Technology"
- Annual Revenue: $5,000,000
- Primary Contact: "John Doe"
User clicks "Save"Step 2: UI Layer Sends Request
// ObjectUI dispatches an action to ObjectOS
const request = {
action: 'create',
object: 'customer',
data: {
name: 'Acme Corp',
industry: 'technology',
annual_revenue: 5000000,
primary_contact: 'contact_12345',
},
};Step 3: ObjectOS Validates Permissions
// Kernel checks: Does this user have permission?
const user = await Auth.getCurrentUser();
const canCreate = await Permission.check({
user,
object: 'customer',
operation: 'create',
});
if (!canCreate) {
throw new Error('Permission denied');
}Step 4: ObjectOS Validates Data
// Kernel asks ObjectQL: Is this data valid?
const schema = ObjectQL.getSchema('customer');
const validation = schema.validate(request.data);
if (!validation.success) {
throw new ValidationError(validation.errors);
}Step 5: ObjectQL Writes to Database
// ObjectQL compiles the request into a database operation
const driver = ObjectQL.getDriver(); // Postgres, MongoDB, etc.
const result = await driver.insert('customer', {
name: 'Acme Corp',
industry: 'technology',
annual_revenue: 5000000,
primary_contact_id: 'contact_12345',
});Step 6: ObjectOS Triggers Workflows
// Kernel checks: Are there any workflows for this event?
const workflows = Workflow.getTriggersFor('customer', 'after_create');
for (const workflow of workflows) {
if (workflow.conditionsMet(result)) {
await workflow.execute(result);
}
}
// In this case:
// ✅ Annual revenue > $1M
// → Assign to enterprise sales team
// → Send alert email to leadershipStep 7: ObjectUI Updates Display
// Kernel returns success response
// UI optimistically updates the screen
// UI shows toast notification: "Customer created successfully"
// UI navigates to the new customer detail pageThe Full Stack in Action
Here's how all three protocols collaborate for a Kanban Board feature:
1. ObjectQL: Define the Data
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Opportunity = ObjectSchema.create({
name: 'opportunity',
label: 'Opportunity',
icon: 'target',
fields: {
title: Field.text({
label: 'Title',
required: true,
}),
stage: Field.select({
label: 'Stage',
options: [
{ label: 'Prospecting', value: 'prospecting', default: true },
{ label: 'Qualification', value: 'qualification' },
{ label: 'Proposal', value: 'proposal' },
{ label: 'Closed Won', value: 'closed_won' },
],
}),
amount: Field.currency({
label: 'Amount',
scale: 2,
}),
customer: Field.lookup('customer', {
label: 'Customer',
}),
},
});2. ObjectOS: Define Business Rules
export const OpportunityWorkflow = Workflow({
object: 'opportunity',
trigger: 'field_update',
conditions: [
{ field: 'stage', operator: 'equals', value: 'closed_won' },
],
actions: [
{ type: 'create_invoice', params: { object: 'invoice' } },
{ type: 'send_notification', params: { to: 'sales_team' } },
],
});3. ObjectUI: Define the Kanban View
export const OpportunityKanban = ListView({
object: 'opportunity',
type: 'kanban',
groupBy: 'stage',
columns: [
{ field: 'title' },
{ field: 'amount' },
{ field: 'customer' },
],
enableDragDrop: true,
});The Result
When a user drags an opportunity card from "Proposal" to "Closed Won":
- ObjectUI captures the drag-drop event
- ObjectOS checks if the user has permission to update the
stagefield - ObjectQL validates that
"closed_won"is a valid option - ObjectQL writes the update to the database
- ObjectOS triggers the workflow (create invoice, send notification)
- ObjectUI updates the kanban board to reflect the new state
All from metadata. Zero hardcoded logic.
Benefits of the Three-Layer Architecture
1. Technology Independence
Swap implementations without breaking the system:
Same Metadata Definitions
↓
┌─────────┼─────────┐
ObjectQL: │ │
Postgres │ MongoDB
│
ObjectOS: │
Node.js │ Python
│
ObjectUI: │
React │ Flutter2. Parallel Development
Teams can work independently on each layer:
- Data Team: Define objects in ObjectQL
- Backend Team: Build workflows in ObjectOS
- Frontend Team: Create views in ObjectUI
All communicate through protocol contracts, not code dependencies.
3. Incremental Migration
Adopt ObjectStack gradually:
- Phase 1: Use ObjectQL as an ORM replacement
- Phase 2: Add ObjectOS for permissions and workflows
- Phase 3: Build ObjectUI views to replace custom forms
Each layer is independently useful.
4. Testability
Mock any layer for testing:
// Test ObjectOS workflows without a real database
const mockObjectQL = {
getSchema: () => CustomerSchema,
insert: jest.fn(),
};
// Test ObjectUI rendering without a real backend
const mockObjectOS = {
checkPermission: () => true,
executeQuery: () => mockData,
};Summary
| Layer | Role | Knows About | Doesn't Know About |
|---|---|---|---|
| ObjectQL | Data structure & queries | Schema, fields, drivers | Users, permissions, UI |
| ObjectOS | Runtime & governance | Auth, workflows, events | Data structure, UI layout |
| ObjectUI | Presentation & interaction | Layout, navigation, actions | Business logic, data storage |
The three protocols are loosely coupled but tightly integrated:
- They communicate through standard contracts (Zod schemas)
- They can be swapped or upgraded independently
- They form a complete system when combined
Next Steps
- ObjectQL: Data Protocol - Full data protocol specification
- ObjectUI: UI Protocol - Full view protocol specification
- ObjectOS: System Protocol - Full control protocol specification
- Developer Guide - Build your first ObjectStack application
Appendix: Protocol Dependencies
Understanding the dependency chain helps you design applications correctly.
Data Layer (ObjectQL)
Field Protocol (Core)
↓
├→ Object Protocol (uses Fields)
├→ Query Protocol (references Fields)
├→ Filter Protocol (filters on Fields)
└→ Validation Protocol (validates Fields)
↓
└→ Hook Protocol (extends validation with code)UI Layer (ObjectUI)
Theme Protocol (Foundation)
↓
View Protocol (uses Object, Query)
├→ ListView (uses Query, Filter)
├→ FormView (uses Object, Field)
└→ Dashboard (uses View, Widget)
↓
├→ Page Protocol (composes Views)
└→ App Protocol (organizes Pages)
↓
└→ Action Protocol (UI interactions)System Layer (ObjectOS)
Driver Protocol (Database Abstraction)
↓
Datasource Protocol (Connection Config)
↓
├→ Context Protocol (Runtime State)
├→ Events Protocol (Event Bus)
└→ Plugin Protocol (Extensibility)
↓
├→ Security Protocol (Access Control)
├→ API Protocol (External Access)
└→ Automation Protocol (Workflows)
↓
└→ AI Protocol (Intelligence)Appendix: Usage Patterns
Pattern 1: Data-Driven UI
UI auto-generated from data definitions:
Object → Field → View → Page → AppPattern 2: Custom UI with Data Binding
Custom UI connected to data:
Page → Component → View (Custom) → Query → ObjectPattern 3: Automation & Integration
Business logic automation:
Object → Hook → Flow → Automation → API/WebhookPattern 4: AI-Enhanced Applications
AI capabilities on top of data:
Object → RAG Pipeline → Agent → Conversation → UI