Skip to content

CO2 Intents

Intents are the core command abstraction in the CO2 system. They represent high-level user and system commands that express why an action is being performed, rather than how. In the event sourcing architecture, intents are the entry point that get routed by policy engines to appropriate domain directives.

An Intent is a user-initiated command that captures the intention behind a state change. Rather than directly mutating data, intents are:

  • Domain-agnostic: They describe user intention without prescribing implementation details
  • Cross-cutting: A single intent can trigger multiple domain aggregates through policies
  • Auditable: Every intent is logged, creating a complete audit trail of user actions
  • Reversible: Undo/redo operations work at the intent level, not individual events

In the CO2 system:

User Action → Intent (user intention) → Policy Router → Directives (implementation) → Domain Events

Intents ask “what should happen?” Directives answer “how should we make it happen?”

All intents extend Intent from nomos_core:

abstract class Intent implements IntentBase {
late String _id;
@override
String get id => _id; // Unique intent ID (UUIDv7)
@override
Map<String, dynamic> toJson(); // Serialization
static Intent fromJson(Map<String, dynamic> json); // Deserialization
NomosIntentDescription describe(NomosIntentDescribeCtx ctx); // Human-readable description
}

Every intent follows a consistent pattern:

import 'package:nomos_core/nomos_core.dart';
import 'package:contracts_v1/contracts_v1.dart';
class CreateTrackableAssetIntent extends Intent {
@override
Type get intentType => CreateTrackableAssetIntent;
final TrackableAssetId? assetId;
final AssetNickname name;
final AssetDescription? description;
final AssetType assetType;
final UserId createdByUserId;
final CustomFieldMap customFields;
CreateTrackableAssetIntent({
this.assetId,
required this.name,
this.description,
required this.assetType,
required this.createdByUserId,
CustomFieldMap? customFields,
}) : customFields = customFields ?? CustomFieldMap.empty();
@override
Map<String, dynamic> toJson() => {
'id': id,
'assetId': assetId?.value,
'name': name.value,
'description': description?.value,
'assetType': assetType.code,
'createdByUserId': createdByUserId.value,
'customFields': customFields.toJson(),
'serialisationTargetClassName': 'CreateTrackableAssetIntent',
};
static CreateTrackableAssetIntent fromJson(Map<String, dynamic> json) {
return CreateTrackableAssetIntent(
assetId: json['assetId'] != null
? TrackableAssetId.fromString(json['assetId'] as String)
: null,
name: AssetNickname.fromString(json['name'] as String?),
description: json['description'] != null
? AssetDescription.fromString(json['description'] as String?)
: null,
assetType: AssetType.fromCode(json['assetType'] as String),
createdByUserId: UserId.fromString(json['createdByUserId'] as String),
customFields: json['customFields'] != null
? CustomFieldMap.fromJson(json['customFields'] as Map<String, dynamic>)
: null,
);
}
@override
String toString() => 'CreateTrackableAssetIntent($name: $assetType)';
@override
NomosIntentDescription describe(NomosIntentDescribeCtx ctx) {
final typeLabel = assetType.code != 'generic' ? ' (${assetType.code})' : '';
return NomosIntentDescription(
template: '{actor} created asset {assetName}{typeLabel}',
variables: {
'actor': actorName(ctx, fallback: createdByUserId.value),
'assetName': name.value,
'typeLabel': typeLabel,
},
);
}
}

All intents must be registered with the Nomos type and factory registries:

// In registration.dart
void registerIntentsV1() {
TypeRegistry.register<CreateTrackableAssetIntent>();
IntentFactoryRegistry.register<CreateTrackableAssetIntent>(
(json, id) => CreateTrackableAssetIntent.fromJson(json),
);
// ... register all other intents
}

The describe() method generates audit-friendly descriptions using templates:

@override
NomosIntentDescription describe(NomosIntentDescribeCtx ctx) {
return NomosIntentDescription(
template: '{actor} granted {user} the {role} role on {resource}',
variables: {
'actor': actorName(ctx, fallback: grantedBy.value),
'user': userName(ctx, userId: targetUserId.value, fallback: targetUserId.value),
'role': role.value,
'resource': resourceName(ctx, resourceType: resourceType.name, resourceId: resourceId.value),
},
);
}

The system renders these as: “Alice granted Bob the admin role on Estate XYZ”

The CO2 intents package (intents_v1) organizes intents into logical domains:

Intents for creating, updating, and managing trackable assets:

IntentPurpose
CreateTrackableAssetIntentCreate a new asset with properties like name, type, and location
UpdateTrackableAssetIntentModify asset details, structural assignment, or custom fields
DeleteTrackableAssetIntentRemove an asset from the system
BulkUpdateTrackableAssetsIntentUpdate multiple assets in a single operation
MoveTrackableAssetsIntentRelocate assets to different structural locations
CreateTrackableAssetCommentIntentAdd a comment/note to an asset

Example:

final intent = CreateTrackableAssetIntent(
name: AssetNickname('HVAC Unit A'),
assetType: AssetType.fromCode('hvac'),
createdByUserId: userId,
structuralAssignment: StructuralAssignment(
roomId: RoomId('room-123'),
),
);

Intents for managing the physical/logical hierarchy (estates, sites, buildings, rooms):

IntentPurpose
CreateEstateIntentCreate a new estate (top-level organizational unit)
DeleteEstateIntentRemove an estate
CreateSiteWithTopologyDraftIntentCreate a site with an initial topology draft
CreateBuildingIntentAdd a building to a site
CreateRoomIntentCreate a room within a building
PublishSiteTopologyIntentFinalize and publish topology changes
SetSiteAnchorIntentDefine geographic anchor point for a site
SetBuildingFootprintIntentDefine building boundary polygon
AddFloorPlanTileImageIntentAttach a floor plan image to a tile

Example:

final intent = CreateEstateIntent(
estateId: EstateId('estate-001'),
name: EstateName('Corporate Campus'),
address: PostalAddress(...),
);

Intents for managing users, permissions, and organizations:

IntentPurpose
CreateUserIntentRegister a new user account
CreateOrganizationIntentCreate a new organization
AddUserToOrganizationIntentAdd user to an organization
GrantPermissionToUserIntentGrant specific permissions to a user
RevokePermissionFromUserIntentRevoke user permissions
GrantEstateAccessIntentGrant user access to an estate
RevokeEstateAccessIntentRevoke estate access
GrantSiteAccessIntentGrant user access to a site
RevokeSiteAccessIntentRevoke site access
UpdateUserPermissionRoleIntentChange a user’s role

Example:

final intent = GrantPermissionToUserIntent(
targetUserId: UserId('user-456'),
resourceType: ResourceType.estate,
resourceId: ResourceId('estate-001', ResourceType.estate),
role: Role.estateAdmin,
grantedBy: currentUserId,
reason: PermissionReasonDescription('Team lead for building maintenance'),
);

Intents for managing documents, images, and media files:

IntentPurpose
CreateAttachmentIntentUpload a file (V1 - deprecated, embeds bytes)
CreateAttachmentIntentV2Upload a file (V2 - recommended, uses blob reference)
DeleteAttachmentIntentRemove an attachment
MoveAttachmentBetweenFoldersIntentReorganize attachments
DetachAttachmentIntentDissociate attachment from a target
TranscribeVoiceNoteIntentConvert voice recording to text (server-only)
UpdateVoiceNoteTranscriptionIntentUpdate transcription results
CreateFolderIntentCreate an attachment folder
DeleteFolderIntentRemove a folder

Example (V2 - Recommended):

// First: upload blob
final blobRef = await nomos.blob!.uploadBlob(
bytes: pdfBytes,
contentType: 'application/pdf',
);
// Then: create intent with blob reference
final intent = CreateAttachmentIntentV2(
blobRef: blobRef,
fileName: FileName('compliance_report.pdf'),
createdBy: userId,
);

Intents for managing user licenses and seat assignments:

IntentPurpose
CreatePersonalLicenceIntentAssign a personal license to a user
CreateOrganizationalLicenceIntentCreate an organizational license
AssignSeatToUserIntentAllocate an org license seat to a user

Example:

final intent = CreatePersonalLicenceIntent(
userId: UserId('user-123'),
requestedBy: adminUserId,
reason: 'New team member onboarding',
);

Intents for managing change proposals and action items:

IntentPurpose
CreateProposalIntentCreate a detailed proposal for changes
SubmitProposalIntentSubmit proposal for review
ApproveProposalIntentApprove a proposed change
CreateResponsibilityIntentCreate an action item/responsibility
UpdateResponsibilityStatusIntentTrack responsibility progress
CreateResponsibilitiesFromProposalIntentAuto-create tasks from approved proposal

Example:

final intent = CreateProposalIntent(
proposalId: ProposalId('prop-001'),
title: ProposalTitle('HVAC System Upgrade'),
description: ProposalDescription('Replace aging HVAC units...'),
proposalType: ProposalType('maintenance'),
createdBy: userId,
estimatedCost: 15000.0,
estimatedDuration: ProposalDuration('3 months'),
targetImplementationDate: ProposalTargetDate('2024-Q3'),
);

Intents for managing asset catalogues and bulk asset creation:

IntentPurpose
CreateCatalogueIntentCreate a new asset catalogue
CreateCatalogueListingIntentAdd an asset template to catalogue
CreateAssetFromListingIntentCreate individual asset from listing
BulkCreateAssetsFromListingIntentCreate multiple assets from same template
UpdateListingAggregateIntentModify a catalogue listing
DeleteCatalogueListingIntentRemove listing from catalogue

Example:

final intent = CreateAssetFromListingWithPositionIntent(
listingId: CatalogueListingId('listing-123'),
structuralAssignment: StructuralAssignment(roomId: RoomId('room-456')),
customNickname: AssetNickname('HVAC Unit B'),
createdBy: userId,
);

Intents for managing custom properties on layers:

IntentPurpose
DefineCustomFieldIntentCreate a new custom field definition
AttachCustomFieldToLayerIntentAdd custom field to a site layer
DetachCustomFieldFromLayerIntentRemove custom field from a layer
SetLayerDefaultCustomFieldsIntentSet default values for a layer
BulkApplyLayerDefaultsIntentApply defaults to multiple assets

Intents for generating reports and insights:

IntentPurpose
TriggerAssetInsightsIntentGenerate asset analytics/KPIs
CalculateAssetKPIsIntentCompute key performance indicators
CreateAssetInsightsDashboardIntentCreate custom dashboard
GenerateEstateSummaryIntentCreate estate overview report
CreateDrawingExportIntentExport drawing/plan as file
CreateVoiceNoteArchiveIntentArchive voice notes
CreateEstateMediaArchiveIntentCreate media backup

Intents for managing retrofit funding and grant programs:

IntentPurpose
CreateFundingProgramIntentDefine a new funding initiative
ApplyForFundingIntentSubmit funding application
ProcessFundingApplicationIntentApprove/reject application
TrackFundingUtilizationIntentMonitor spending against budget
DefineFundingProgramIntent(Perseus-specific) Define funding rules
SubmitFundingProgramClaimIntentSubmit claim for reimbursement
CompleteClaimVerificationIntentVerify and approve claim
AwardBenefitIntentDistribute funding benefit

Intents for managing asset type taxonomies:

IntentPurpose
CreateTaxonomyIntentCreate a new taxonomy
AddTaxonomyCategoryIntentAdd category to taxonomy
AddTaxonomySubCategoryIntentAdd subcategory
AddTaxonomyCustomFieldIntentDefine custom field for category
(and corresponding update/remove intents)Modify or delete taxonomy elements
1. Client Creates Intent
2. Intent Serialized (toJson) & Sent
3. Server Receives & Deserializes (fromJson)
4. Intent Registered in Ledger
5. Policy Engine Routes Intent → Directives
6. Directives Executed → Domain Events Generated
7. Events Applied to Aggregates
8. Snapshots Updated
// Create and dispatch an intent
final intent = CreateTrackableAssetIntent(
name: AssetNickname('New Asset'),
assetType: AssetType.fromCode('generic'),
createdByUserId: currentUserId,
);
// Dispatch through Nomos
await nomos.dispatch(intent);

Intents must be registered before the system starts:

// Initialize the intents module
final intentsModule = IntentsV1();
intentsModule.registerIntents();
// Now intents can be dispatched
final intent = CreateTrackableAssetIntent(...);
await nomos.dispatch(intent);

Some intents require server-side execution (cannot run on client):

// Example: voice transcription (requires OpenAI Whisper)
class TranscribeVoiceNoteIntent extends Intent {
// ...
@override
bool get requiresServerExecution => true;
}

When dispatched from a client, server-only intents are routed through ServerIntentTransport to a Cloud Run instance.

CreateTrackableAssetIntent(
name: AssetNickname('HVAC Unit'),
assetType: AssetType.fromCode('hvac'),
createdByUserId: userId,
structuralAssignment: StructuralAssignment(roomId: roomId),
customFields: CustomFieldMap({'temperature_setpoint': '72'}),
)
GrantPermissionToUserIntent(
targetUserId: UserId('user-456'),
resourceType: ResourceType.estate,
resourceId: ResourceId('estate-001', ResourceType.estate),
role: Role.estateAdmin,
grantedBy: currentUserId,
expiresAt: DateTime.now().add(Duration(days: 365)),
)
CreateEstateIntent(
estateId: EstateId('estate-001'),
name: EstateName('Corporate Campus'),
address: PostalAddress(
street: 'Main St',
city: 'Boston',
country: 'US',
),
)
CreateSiteWithTopologyDraftIntent(
siteId: SiteId('site-001'),
siteName: SiteName('Building A'),
estateId: EstateId('estate-001'),
initialBuilding: CreateBuildingPayload(
name: 'Main Structure',
levelCount: 3,
),
)
// Step 1: Upload to blob store
final blobRef = await nomos.blob!.uploadBlob(
bytes: fileBytes,
contentType: 'application/pdf',
);
// Step 2: Create intent with reference
CreateAttachmentIntentV2(
blobRef: blobRef,
fileName: FileName('document.pdf'),
createdBy: userId,
)

Always use domain types (not String):

// Good
final intent = CreateTrackableAssetIntent(
name: AssetNickname('Unit A'),
createdByUserId: UserId('user-123'),
);
// Bad - avoids type safety
final intent = CreateTrackableAssetIntent(
name: AssetNickname('Unit A'),
createdByUserId: UserId('user-123'), // Use UserId, not String
);

Always use CreateAttachmentIntentV2 for new code:

// Good - V2 with blob reference
final intent = CreateAttachmentIntentV2(
blobRef: blobRef,
fileName: FileName('doc.pdf'),
createdBy: userId,
);
// Avoid - V1 embeds bytes
final intent = CreateAttachmentIntent(
fileBytes: largeFileData,
fileName: FileName('doc.pdf'),
createdBy: userId,
);

Intents track who initiated the action:

CreateTrackableAssetIntent(
name: AssetNickname('Asset'),
assetType: AssetType.fromCode('generic'),
createdByUserId: currentUserId, // Always set this
)

When creating proposals or responsibilities, use clear titles:

// Good
title: ProposalTitle('Replace HVAC Unit A - Expected Downtime 2hrs'),
// Avoid vague titles
title: ProposalTitle('HVAC change'),

For large datasets, use bulk intents:

// Good - single intent
BulkUpdateTrackableAssetsIntent(
assetIds: [asset1, asset2, asset3],
updates: {...},
)
// Avoid - creates overhead
for (final assetId in assetIds) {
await nomos.dispatch(UpdateTrackableAssetIntent(...));
}

Intents are serialized and sent, so validate early:

if (name.value.isEmpty) {
throw ArgumentError('Asset name cannot be empty');
}
if (structuralAssignment != null && roomId == null) {
throw ArgumentError('Room ID required for structural assignment');
}
final intent = CreateTrackableAssetIntent(...);
await nomos.dispatch(intent);

Understanding the three levels:

AspectIntentDirectiveEvent
LevelUser-facing commandImplementation instructionImmutable fact
SourceUser action or system triggerPolicy engineCommand execution
PurposeExpress user intentionSpecify how to implement intentRecord what happened
MutabilityCan be rolled backDetermines logicImmutable history
ExampleCreateTrackableAssetIntentProvisionAssetDirectiveAssetCreatedEvent

Flow:

Intent (user says "create asset")
↓ (policy routes)
Directives (says "provision asset, assign to room, update inventory")
↓ (execution)
Events (fact: "asset created", "asset assigned", "inventory updated")

All intents must be JSON-serializable for storage and network transmission. The serialisationTargetClassName field enables proper deserialization:

@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name.value,
'serialisationTargetClassName': 'CreateTrackableAssetIntent', // Required
};

Once created and dispatched, intents cannot be modified. They represent a historical record of user intent. Updates require new intents (e.g., UpdateTrackableAssetIntent).

A single intent can affect multiple aggregates through policy routing. The policy engine ensures consistency:

CreateSiteWithTopologyDraftIntent
→ Creates: SiteRootAggregate + TopologyDraftAggregate + multiple BuildingAggregates
→ Maintains: referential integrity across aggregates

Problem: Intent dispatch fails silently

await nomos.dispatch(intent); // No error, but no effect

Solution: Ensure intent is registered:

TypeRegistry.register<YourIntent>();
IntentFactoryRegistry.register<YourIntent>(
(json, id) => YourIntent.fromJson(json),
);

Problem: ArgumentError: JSON must contain a "serialisationTargetClassName" field

Solution: Verify toJson() includes the serialization field:

@override
Map<String, dynamic> toJson() => {
'id': id,
'serialisationTargetClassName': 'YourIntent', // Add this
// ... other fields
};

Problem: TranscribeVoiceNoteIntent called from Flutter client throws error

Solution: Server-only intents must be routed through server transport:

// Make sure ServerIntentTransport is configured
final nomos = NomosConfig()
.withServerIntentTransport(...)
.build();
await nomos.dispatch(TranscribeVoiceNoteIntent(...)); // Routes to server
  • Package: intents_v1 at /dart_packages/co2/intents/intents_v1/
  • Base Class: Intent in nomos_core
  • Registry: registration.dart - All intent registrations
  • Utilities: describe_utils.dart - Intent description helpers