Define a task

Author a custom task definition

This guide describes best practices for authoring custom task definitions for use in Lattice. Custom task definitions allow you to extend Lattice’s tasking capabilities with your own specialized workflows.

Before you begin

Authoring tasks

A custom task is a Protobuf message type that contains the information your asset requires to execute a task autonomously. The complexity of your task definition depends on your integration’s requirements. For example:

  • Simple tasks: A sensor may define an on/off toggle message
  • Entity-targeted tasks: A tracking system may define a message containing an entity_id to track
  • Complex tasks: An autonomous vehicle may define multiple parameters including waypoints, speed limits, sensor configurations, and priority levels

Custom tasks give you the flexibility to model domain-specific workflows while maintaining type safety and compatibility across your Lattice deployment.

Create a package

Define your tasks as Protobuf messages as part of a package. A package should contain all relevant message types to compose tasks that express an integration’s capabilities. Naming your package clearly and precisely is crucial to avoid potential naming conflicts.

Use a consistent package naming pattern: org.repository.package.version

Where:

  • org: Your organization name in the Schema Registry.
  • repository: Your repository name in the Schema Registry.
  • package: A descriptive name for your task domain, for example, surveillance, navigation, or sensor.
  • version: The API version, for example, v1, v2 v1alpha or v2test.
1// Example
2package company.isr.imaging.v1test;

During early development, mark your package as unstable by appending a stability marker (alpha, beta, or test) to the version.

1package org.example.surveillance.v1alpha;
2package org.example.navigation.v2beta;

The Schema Registry enforces breaking change detection on all packages, regardless of the stability market. If you need to make a breaking change, you must cut a new version (i.e. v1alpha -> v2alpha).

Fully qualified names

The fully qualified name of a message is the package name with the message name:

1package company.isr.imaging.v1test;
2
3message AerialSurveillance {
4 // ...
5}

For the example above, this would create the fully qualified name: company.isr.imaging.v1test.AerialSurveillance.

All protobuf type URLs are prefixed with type.googleapis.com. You will always reference your task definition by its type URL, for example: type.googleapis.com/company.isr.imaging.v1test.AerialSurveillance.

Globally unique names: Each fully qualified message name must be globally unique within the Schema Registry. This prevents naming conflicts when multiple organizations publish schemas.

For more information about Protobuf packages and naming, see Protobuf files and packages in the Buf documentation.

Breaking change protection

The Schema Registry enforces breaking change detection for all packages. This ensures backward compatibility for consumers of your schemas. You can deprecate old fields, or if necessary, release a new version of your package.

Common breaking changes that are prevented:

  • Removing or renaming fields
  • Changing field types
  • Changing field numbers
  • Removing enumerated values

Non-breaking changes that are allowed:

  • Adding new fields
  • Adding new enumerated values
  • Adding new messages
  • Deprecating fields (using the deprecated option)

For a complete guide on breaking changes, see the Buf breaking change detector documentation.

Define a custom schema

Protobuf’s Any type enables creating flexible, extensible schemas. It allows you to embed arbitrary serialized protocol buffer messages in an existing schema:

1message Any {
2 // A resource name that uniquely identifies
3 // the type of the serialized protocol buffer message.
4 string type_url = 1;
5
6 // The serialized protocol buffer message.
7 bytes value = 2;
8}

The Lattice SDK uses the Any type as an entrypoint for custom task specifications. Create a new file called surveillance.proto with the following contents:

surveillance.proto
1syntax = "proto3";
2
3package org.example.surveillance.v1alpha;
4
5import "google/protobuf/wrappers.proto";
6
7// AerialSurveillance represents a task to perform aerial surveillance of an area or entity.
8// This task specification is used with autonomous aerial assets to define surveillance parameters.
9message AerialSurveillance {
10 // Entity to perform surveillance on. Optional - if not provided, the task
11 // applies to the entire surveillance area.
12 string entity_id = 1;
13
14 // Surveillance pattern to use during the mission.
15 SurveillancePattern pattern = 2;
16
17 // Minimum altitude in meters for the surveillance.
18 google.protobuf.FloatValue min_altitude_m = 3;
19
20 // Maximum altitude in meters for the surveillance.
21 google.protobuf.FloatValue max_altitude_m = 4;
22
23 // Priority of the surveillance mission (0-100, where 100 is highest priority).
24 google.protobuf.UInt32Value priority = 5;
25}
26
27// Different patterns that can be used for aerial surveillance.
28enum SurveillancePattern {
29 // Invalid default value, must not be used.
30 SURVEILLANCE_PATTERN_INVALID = 0;
31
32 // Grid pattern covers the area systematically in a grid formation.
33 SURVEILLANCE_PATTERN_GRID = 1;
34
35 // Spiral pattern moves outward from a central point.
36 SURVEILLANCE_PATTERN_SPIRAL = 2;
37
38 // Random pattern provides unpredictable coverage.
39 SURVEILLANCE_PATTERN_RANDOM = 3;
40
41 // Perimeter pattern follows the boundary of the surveillance area.
42 SURVEILLANCE_PATTERN_PERIMETER = 4;
43}
Best practices
  • Use descriptive comments: Document each field and enum value to help other developers understand your schema
  • Use wrapper types: For optional numeric fields, use google.protobuf.*Value wrapper types instead of primitive types
  • Start with stability markers: Use v1alpha or v1beta during development to allow breaking changes
  • Reserved field 0: Always reserve enum value 0 for an INVALID or UNSPECIFIED variant

Common patterns

sensor.proto
1syntax = "proto3";
2
3package company.sensor.v1;
4
5// Use empty messages to issue simple commands to your Asset
6message SensorOn {}
7
8// Stop Detecting
9message SensorOff {}
sensor.proto
1syntax = "proto3";
2
3package company.sensor.v1;
4
5// Use the entity_id field task your Asset against an Entity
6message TrackEntity {
7 string entity_id = 1;
8}
flight.proto
1syntax = "proto3";
2
3package company.uas.v1;
4
5// Use Message types to compose a more complex Task
6message Coordinate {
7 double lat = 1;
8 double lon = 2;
9}
10
11// A target could be either a lat/lon, or an Entity that we can track
12message Target {
13 oneof target {
14 string entity_id = 1;
15 Coordinate coordinate = 2;
16 }
17}
18
19message FlightConstraints {
20 float min_altitude_agl_m = 1;
21 float max_altitude_agl_m = 2;
22 float max_ias_kts = 3;
23 float min_ias_kts = 4;
24}
25
26enum FlightPattern {
27 FLIGHT_PATTERN_GRID = 1;
28 FLIGHT_PATTERN_ORBIT = 2;
29 FLIGHT_PATTERN_SECTOR_SEARCH = 3;
30}
31
32// Task Message
33message Survey {
34 Target target = 1;
35 FlightPattern flight_pattern = 2;
36 FlightConstraints flight_constraints = 3;
37}

What’s next?