Command agents and operate on tasks

Creating, monitoring, and managing tasks using the Lattice SDK

Operators use the Tasks API to create, monitor, and manage tasks across their operational domain.

The specific tasks you command, and monitor, in Lattice depend on the mission requirements, and the environment you’re integrating with. This page shows you how to use the Lattice Sandboxes to create, monitor, and cancel tasks.

The CreateTask (REST) API lets you create tasks in Lattice using custom task definitions published to the Lattice Schema Registry. The StreamTasks (REST | gRPC) API provides centralized visibility into task creation, updates, and status changes for all tasks in your environment. The CancelTask (REST | gRPC) API allows you to request cancellation of tasks that are no longer needed.

Before you begin

  • To create, monitor, or cancel tasks, set up your Lattice environment.
  • Familiarize yourself with tasks and the task lifecycle.
  • To create custom tasks, publish your schemas to the Lattice Schema Registry (LSR).

Create tasks using REST

When working with custom tasks using REST:

  1. First, you retrieve your JSON Schema definitions from the Schema Registry.
  2. Then you validate your task data against these schema definitions.
  3. Finally, you use the validated data to create tasks in Lattice using the CreateTask API.

The Lattice Schema Registry (LSR) lets you validate your task data using JSON schema:

1

Log in to the LSR using your credentials.

2

Navigate to your organization’s repositories.

3

Select the repository that contains your custom task definitions.

4

Browse to the specific protobuf definition you want to use for your task.

5

Select the plugin dropdown and choose JSON Schema to view the available JSON Schema files.

6

Download the JSON schema files:

$curl -H "Authorization: Bearer $BUF_TOKEN" \
> -o myschema.jsonschema.bundle.json \
> https://schema-registry.developer.anduril.com/plugins/<your-organization>/<your-repository>/<your-schema>/jsonschema/bundle

This command downloads the bundled JSON Schema file that includes all referenced schemas. For example: Example JSON Schema structure for a custom task:

1{
2 "$id": "org.example.reconnaissance.v1beta.Objective.jsonschema.json",
3 "$schema": "https://json-schema.org/draft/2020-12/schema",
4 "properties": {
5 "entityId": {
6 "type": "string"
7 },
8 "lla": {
9 "$ref": "org.example.reconnaissance.v1beta.LLA.jsonschema.json"
10 }
11 },
12 "type": "object"
13}

To create a task using a custom task definition, do the following:

1

Prepare your task data

Define the data for your custom task that matches your schema structure. This data will be encapsulated in the specification field of the CreateTaskRequest message.

1task_data = {
2 "lla": {
3 "latitudeDegrees": 37.7749,
4 "longitudeDegrees": -122.4194,
5 "altitudeHaeM": 100.0
6 }
7}
2

Validate task data using the schema

Before sending data to Lattice, validate it against the JSON Schema to ensure it matches your custom task definition. This prevents issues when submitting invalid task data to the API.

1#!/usr/bin/env python3
2import asyncio
3import json
4import os
5import sys
6from anduril import Lattice
7from anduril import (
8 GoogleProtobufAny,
9 Principal,
10 Relations,
11 System,
12 TaskEntity
13)
14from jsonschema import validate # type: ignore[import-untyped]
15
16# Load environment variables
17lattice_endpoint = os.getenv('LATTICE_ENDPOINT')
18environment_token = os.getenv('ENVIRONMENT_TOKEN')
19sandboxes_token = os.getenv('SANDBOXES_TOKEN') # Remove if not using Sandboxes
20
21if not environment_token or not lattice_endpoint:
22 print("Missing required environment variables.")
23 sys.exit(1)
24
25# Initialize Lattice client
26client = Lattice(
27 base_url=f"https://{lattice_endpoint}",
28 token=lambda: environment_token, # type: ignore[arg-type]
29 # Remove the following header if not developing on Sandboxes
30 headers={"anduril-sandbox-authorization": f"Bearer {sandboxes_token}"} if sandboxes_token else None
31)
32
33def load_schema(schema_file):
34 """Load a JSON schema from file."""
35 with open(schema_file, 'r') as f:
36 return json.load(f)
37
38def validate_task_data(task_data, schema_file):
39 """Validate task data against a JSON schema."""
40 schema = load_schema(schema_file)
41
42 try:
43 validate(instance=task_data, schema=schema)
44 print("Task data validation successful")
45 return True
46 except Exception as e:
47 print(f"Validation error: {str(e)}")
48 return False
49
50async def create_task(task_data, agent_id, entity_id=None):
51 """
52 Create a new task using the Lattice SDK.
53
54 Args:
55 task_data: The validated task data
56 agent_id: The entity ID of the agent to route the task to for execution
57 entity_id: Optional entity ID to use as the initial entity
58 """
59 description = "Custom Task Definition"
60 specification_type = "type.googleapis.com/org.example.reconnaissance.v1beta.Objective"
61 specification = GoogleProtobufAny(
62 type=specification_type,
63 **task_data
64 )
65 author = Principal(system=System(service_name="example-service"))
66 # Route the task to the agent by setting it as the assignee.
67 relations = Relations(assignee=Principal(system=System(entity_id=agent_id)))
68
69 try:
70 # Set up initial entities if an entity ID was provided
71 initial_entities = None
72 if entity_id:
73 try:
74 print(f"Fetching entity with ID: {entity_id}")
75 entity = client.entities.get_entity(entity_id)
76
77 if entity:
78 task_entity = TaskEntity(entity=entity)
79 initial_entities = [task_entity]
80 print(f"Using entity {entity_id} as initial entity for task")
81 except Exception as e:
82 print(f"Error fetching entity {entity_id}: {str(e)}")
83
84 # Create the task with the validated data
85 print("Creating task...")
86 response = client.tasks.create_task(
87 description=description,
88 specification=specification,
89 author=author,
90 relations=relations,
91 is_executed_elsewhere=False,
92 initial_entities=initial_entities
93 )
94
95 if response.version:
96 print(f"Task created successfully with ID: {response.version.task_id}")
97 return response
98 except Exception as e:
99 print(f"Error creating task: {str(e)}")
100 return None
101
102async def main():
103 # Example task data for a reconnaissance objective: a fixed geodetic point.
104 task_data = {
105 "lla": {
106 "latitudeDegrees": 37.7749,
107 "longitudeDegrees": -122.4194,
108 "altitudeHaeM": 100.0
109 }
110 }
111
112 # Path to the JSON schema file (downloaded from Schema Registry)
113 schema_file = 'org.example.reconnaissance.v1beta.Objective.jsonschema.bundle.json'
114
115 # Validate the task data (skip if schema file not found)
116 if os.path.isfile(schema_file):
117 if not validate_task_data(task_data, schema_file):
118 print("Task creation aborted due to validation errors.")
119 return
120 else:
121 print("Schema file not found, skipping validation")
122
123 # Create the task with validated data
124 # Replace with the entity ID of the agent to route the task to.
125 agent_id = "<AGENT_ID>"
126 # You can pass an entity_id here if you want to associate the task with an entity
127 entity_id = None # Replace with an actual entity ID if needed
128 result = await create_task(task_data, agent_id, entity_id)
129 if result:
130 print(f"Task created with status: {result.status}")
131
132if __name__ == "__main__":
133 asyncio.run(main())
3

Create the task using validated data

Use the CreateTask API to submit your task to Lattice. This API expects the following key fields:

  • description: A human-readable description of the task (up to 4096 characters).
  • specification: Your validated task data wrapped in a Google Protocol Buffer Any message.
  • author: Information about who or what created the task.
  • relations: Relationships for the task, such as a parent task or an assignee. Set relations.assignee to the agent’s Principal to route the task to that agent for execution.
1#!/usr/bin/env python3
2import asyncio
3import json
4import os
5import sys
6from anduril import Lattice
7from anduril import (
8 GoogleProtobufAny,
9 Principal,
10 Relations,
11 System,
12 TaskEntity
13)
14from jsonschema import validate # type: ignore[import-untyped]
15
16# Load environment variables
17lattice_endpoint = os.getenv('LATTICE_ENDPOINT')
18environment_token = os.getenv('ENVIRONMENT_TOKEN')
19sandboxes_token = os.getenv('SANDBOXES_TOKEN') # Remove if not using Sandboxes
20
21if not environment_token or not lattice_endpoint:
22 print("Missing required environment variables.")
23 sys.exit(1)
24
25# Initialize Lattice client
26client = Lattice(
27 base_url=f"https://{lattice_endpoint}",
28 token=lambda: environment_token, # type: ignore[arg-type]
29 # Remove the following header if not developing on Sandboxes
30 headers={"anduril-sandbox-authorization": f"Bearer {sandboxes_token}"} if sandboxes_token else None
31)
32
33def load_schema(schema_file):
34 """Load a JSON schema from file."""
35 with open(schema_file, 'r') as f:
36 return json.load(f)
37
38def validate_task_data(task_data, schema_file):
39 """Validate task data against a JSON schema."""
40 schema = load_schema(schema_file)
41
42 try:
43 validate(instance=task_data, schema=schema)
44 print("Task data validation successful")
45 return True
46 except Exception as e:
47 print(f"Validation error: {str(e)}")
48 return False
49
50async def create_task(task_data, agent_id, entity_id=None):
51 """
52 Create a new task using the Lattice SDK.
53
54 Args:
55 task_data: The validated task data
56 agent_id: The entity ID of the agent to route the task to for execution
57 entity_id: Optional entity ID to use as the initial entity
58 """
59 description = "Custom Task Definition"
60 specification_type = "type.googleapis.com/org.example.reconnaissance.v1beta.Objective"
61 specification = GoogleProtobufAny(
62 type=specification_type,
63 **task_data
64 )
65 author = Principal(system=System(service_name="example-service"))
66 # Route the task to the agent by setting it as the assignee.
67 relations = Relations(assignee=Principal(system=System(entity_id=agent_id)))
68
69 try:
70 # Set up initial entities if an entity ID was provided
71 initial_entities = None
72 if entity_id:
73 try:
74 print(f"Fetching entity with ID: {entity_id}")
75 entity = client.entities.get_entity(entity_id)
76
77 if entity:
78 task_entity = TaskEntity(entity=entity)
79 initial_entities = [task_entity]
80 print(f"Using entity {entity_id} as initial entity for task")
81 except Exception as e:
82 print(f"Error fetching entity {entity_id}: {str(e)}")
83
84 # Create the task with the validated data
85 print("Creating task...")
86 response = client.tasks.create_task(
87 description=description,
88 specification=specification,
89 author=author,
90 relations=relations,
91 is_executed_elsewhere=False,
92 initial_entities=initial_entities
93 )
94
95 if response.version:
96 print(f"Task created successfully with ID: {response.version.task_id}")
97 return response
98 except Exception as e:
99 print(f"Error creating task: {str(e)}")
100 return None
101
102async def main():
103 # Example task data for a reconnaissance objective: a fixed geodetic point.
104 task_data = {
105 "lla": {
106 "latitudeDegrees": 37.7749,
107 "longitudeDegrees": -122.4194,
108 "altitudeHaeM": 100.0
109 }
110 }
111
112 # Path to the JSON schema file (downloaded from Schema Registry)
113 schema_file = 'org.example.reconnaissance.v1beta.Objective.jsonschema.bundle.json'
114
115 # Validate the task data (skip if schema file not found)
116 if os.path.isfile(schema_file):
117 if not validate_task_data(task_data, schema_file):
118 print("Task creation aborted due to validation errors.")
119 return
120 else:
121 print("Schema file not found, skipping validation")
122
123 # Create the task with validated data
124 # Replace with the entity ID of the agent to route the task to.
125 agent_id = "<AGENT_ID>"
126 # You can pass an entity_id here if you want to associate the task with an entity
127 entity_id = None # Replace with an actual entity ID if needed
128 result = await create_task(task_data, agent_id, entity_id)
129 if result:
130 print(f"Task created with status: {result.status}")
131
132if __name__ == "__main__":
133 asyncio.run(main())

The response contains a Task object with details including the assigned task ID and initial status, which starts as STATUS_CREATED in the task lifecycle.

Create tasks using gRPC

When you use gRPC, the Protobuf types are generated from your task definition directly, so there’s no separate JSON schema validation step. Instead, the compiler enforces the task’s shape. You build your custom task message, pack it into the specification Any field, and call CreateTask.

1

Build and pack the task specification

Construct your Objective message from the bindings generated from your task definition, then pack it into a google.protobuf.Any. The type URL on the Any is what lets the receiving agent identify and unpack the task:

1# This Python example is compatible with artifacts generated using
2# the following grpc/python plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/python
3
4import os
5import sys
6import grpc
7from auth import ClientCredentialsAuth
8
9from google.protobuf.any_pb2 import Any
10
11from anduril.taskmanager.v1.task.pub_pb2 import (
12 Principal,
13 Relations,
14 System,
15 User,
16)
17from anduril.taskmanager.v1.task_manager_api.pub_pb2 import CreateTaskRequest
18from anduril.taskmanager.v1.task_manager_api.pub_pb2_grpc import TaskManagerAPIStub
19
20# Your custom task from schema-registry.developer.anduril.com
21from org.example.reconnaissance.v1beta.objective_pb2 import (
22 LLA,
23 Objective,
24)
25
26
27def create_task(client, agent_entity_id):
28 """Create an Objective task and route it to an agent."""
29 # Build the custom task message. Objective is a oneof; here we set an LLA point.
30 objective = Objective(
31 lla=LLA(
32 latitude_degrees=37.7749,
33 longitude_degrees=-122.4194,
34 altitude_hae_m=100.0,
35 ),
36 )
37
38 # Pack the task into a google.protobuf.Any for the specification field.
39 specification = Any()
40 specification.Pack(objective)
41
42 request = CreateTaskRequest(
43 description="Reconnaissance objective",
44 specification=specification,
45 # The service creating the task.
46 author=Principal(user=User(user_id="operator@example.com")),
47 # Route the task to the agent by setting it as the assignee.
48 relations=Relations(assignee=Principal(system=System(entity_id=agent_entity_id))),
49 )
50
51 response = client.CreateTask(request)
52 return response.task
53
54
55def main():
56 client_id = os.getenv("LATTICE_CLIENT_ID")
57 client_secret = os.getenv("LATTICE_CLIENT_SECRET")
58 lattice_endpoint = os.getenv("LATTICE_ENDPOINT")
59 sandboxes_token = os.getenv("SANDBOXES_TOKEN")
60
61 if not client_id or not client_secret or not lattice_endpoint or not sandboxes_token:
62 print("Missing required environment variables", file=sys.stderr)
63 sys.exit(1)
64
65 auth = ClientCredentialsAuth(
66 client_id=client_id,
67 client_secret=client_secret,
68 sandboxes_token=sandboxes_token,
69 endpoint=f"https://{lattice_endpoint}/api/v1/oauth/token",
70 )
71
72 credentials = grpc.ssl_channel_credentials()
73 channel = grpc.intercept_channel(
74 grpc.secure_channel(lattice_endpoint, credentials),
75 auth.create_metadata_interceptor(),
76 )
77
78 client = TaskManagerAPIStub(channel)
79
80 # Replace with the entity ID of the agent to route the task to.
81 agent_entity_id = "<AGENT_ID>"
82
83 task = create_task(client, agent_entity_id)
84 print(f"Created task with ID: {task.version.task_id}")
85
86
87if __name__ == "__main__":
88 main()
2

Create the task

Call the CreateTask RPC with the packed specification. Set the following key fields on the CreateTaskRequest:

  • specification: Your custom task message packed into a google.protobuf.Any.
  • author: The Principal — a user or system — creating the task.
  • relations.assignee: The agent’s Principal. Set this to route the task to that agent for execution.
  • description: A human-readable description of the task.
1# This Python example is compatible with artifacts generated using
2# the following grpc/python plugin: https://buf.build/anduril/lattice-sdk/sdks/main:grpc/python
3
4import os
5import sys
6import grpc
7from auth import ClientCredentialsAuth
8
9from google.protobuf.any_pb2 import Any
10
11from anduril.taskmanager.v1.task.pub_pb2 import (
12 Principal,
13 Relations,
14 System,
15 User,
16)
17from anduril.taskmanager.v1.task_manager_api.pub_pb2 import CreateTaskRequest
18from anduril.taskmanager.v1.task_manager_api.pub_pb2_grpc import TaskManagerAPIStub
19
20# Your custom task from schema-registry.developer.anduril.com
21from org.example.reconnaissance.v1beta.objective_pb2 import (
22 LLA,
23 Objective,
24)
25
26
27def create_task(client, agent_entity_id):
28 """Create an Objective task and route it to an agent."""
29 # Build the custom task message. Objective is a oneof; here we set an LLA point.
30 objective = Objective(
31 lla=LLA(
32 latitude_degrees=37.7749,
33 longitude_degrees=-122.4194,
34 altitude_hae_m=100.0,
35 ),
36 )
37
38 # Pack the task into a google.protobuf.Any for the specification field.
39 specification = Any()
40 specification.Pack(objective)
41
42 request = CreateTaskRequest(
43 description="Reconnaissance objective",
44 specification=specification,
45 # The service creating the task.
46 author=Principal(user=User(user_id="operator@example.com")),
47 # Route the task to the agent by setting it as the assignee.
48 relations=Relations(assignee=Principal(system=System(entity_id=agent_entity_id))),
49 )
50
51 response = client.CreateTask(request)
52 return response.task
53
54
55def main():
56 client_id = os.getenv("LATTICE_CLIENT_ID")
57 client_secret = os.getenv("LATTICE_CLIENT_SECRET")
58 lattice_endpoint = os.getenv("LATTICE_ENDPOINT")
59 sandboxes_token = os.getenv("SANDBOXES_TOKEN")
60
61 if not client_id or not client_secret or not lattice_endpoint or not sandboxes_token:
62 print("Missing required environment variables", file=sys.stderr)
63 sys.exit(1)
64
65 auth = ClientCredentialsAuth(
66 client_id=client_id,
67 client_secret=client_secret,
68 sandboxes_token=sandboxes_token,
69 endpoint=f"https://{lattice_endpoint}/api/v1/oauth/token",
70 )
71
72 credentials = grpc.ssl_channel_credentials()
73 channel = grpc.intercept_channel(
74 grpc.secure_channel(lattice_endpoint, credentials),
75 auth.create_metadata_interceptor(),
76 )
77
78 client = TaskManagerAPIStub(channel)
79
80 # Replace with the entity ID of the agent to route the task to.
81 agent_entity_id = "<AGENT_ID>"
82
83 task = create_task(client, agent_entity_id)
84 print(f"Created task with ID: {task.version.task_id}")
85
86
87if __name__ == "__main__":
88 main()

The response contains the created Task, including its assigned task ID.

Monitor tasks

The StreamTasks (REST | gRPC) API establishes a server-sent events (SSE) stream that lets you monitor tasks in Lattice:

1

Initialize the task stream

In the following example, we specify a prefix to filter the task stream with:

1package main
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8
9 Lattice "github.com/anduril/lattice-sdk-go/v4"
10 "github.com/anduril/lattice-sdk-go/v4/client"
11 "github.com/anduril/lattice-sdk-go/v4/option"
12)
13
14func main() {
15 // Get environment variables
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
18 clientId := os.Getenv("LATTICE_CLIENT_ID")
19 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
20
21 // Check required environment variables
22 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
23 fmt.Println("Missing required environment variables")
24 os.Exit(1)
25 }
26
27 // Initialize headers for sandbox authorization
28 headers := http.Header{}
29 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
30 // Create the client
31 LatticeClient := client.NewClient(
32 option.WithClientCredentials(clientId, clientSecret),
33 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
34 option.WithHTTPHeader(headers),
35 )
36
37 fmt.Println("Starting task stream...")
38
39 ctx := context.Background()
40
41 // Create task stream request
42 request := &Lattice.TaskStreamRequest{
43 HeartbeatIntervalMs: Lattice.Int(10000),
44 ExcludePreexistingTasks: Lattice.Bool(false),
45 TaskType: &Lattice.TaskStreamRequestTaskType{
46 TaskStreamRequestTaskTypeTaskTypePrefix: &Lattice.TaskStreamRequestTaskTypeTaskTypePrefix{
47 // Define a prefix filter for tasks that belong to your organization
48 TaskTypePrefix: "type.googleapis.com/<your-organization>.tasks",
49 },
50 },
51 }
52
53 // Start the task stream
54 stream, err := LatticeClient.Tasks.StreamTasks(ctx, request)
55 if err != nil {
56 fmt.Printf("Failed to start task stream: %v\n", err)
57 return
58 }
59 defer stream.Close()
60
61 // Process the stream events as they arrive
62 for {
63 event, err := stream.Recv()
64 if err != nil {
65 fmt.Printf("Error receiving task stream event: %v\n", err)
66 return
67 }
68
69 if event.Event == "heartbeat" {
70 // Process heartbeat events
71 timestamp := *event.GetHeartbeat().GetTimestamp()
72 fmt.Printf("Heartbeat: %v\n", timestamp)
73 } else {
74 task := event.GetTaskEvent().GetTaskEvent().GetTask()
75 taskId := task.GetVersion().GetTaskID()
76 status := task.GetStatus().GetStatus()
77
78 fmt.Printf(" TaskID: %v\n", *taskId)
79 fmt.Printf(" Status: %v\n", *status)
80 }
81 }
82}

The StreamTasks method accepts the following parameters:

heartbeatIntervalMs
Number

The interval in milliseconds at which the server sends heartbeat events (default: 30000 ms). Use heartbeats to verify the connection is still active. The minimum is 1000 ms; smaller values are raised to 1000 ms.

excludePreexistingTasks
Boolean

When set to true, the stream will only include tasks created after the stream starts. When false (default), existing tasks will also be included in the stream.

taskType
Object

Specifies which task types to include in the stream. Can filter by exact match or by prefix.

taskTypePrefix
String

Only include tasks whose type starts with the specified prefix. For example, type.googleapis.com/<your-organization.tasks>.

taskTypeUrls
List

Only include tasks specified in the list. You must list the full URL of the task definition for each of the tasks you want to filter from the stream.

2

Process stream events

Process the different types of events that arrive through the stream:

1package main
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8
9 Lattice "github.com/anduril/lattice-sdk-go/v4"
10 "github.com/anduril/lattice-sdk-go/v4/client"
11 "github.com/anduril/lattice-sdk-go/v4/option"
12)
13
14func main() {
15 // Get environment variables
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
18 clientId := os.Getenv("LATTICE_CLIENT_ID")
19 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
20
21 // Check required environment variables
22 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
23 fmt.Println("Missing required environment variables")
24 os.Exit(1)
25 }
26
27 // Initialize headers for sandbox authorization
28 headers := http.Header{}
29 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
30 // Create the client
31 LatticeClient := client.NewClient(
32 option.WithClientCredentials(clientId, clientSecret),
33 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
34 option.WithHTTPHeader(headers),
35 )
36
37 fmt.Println("Starting task stream...")
38
39 ctx := context.Background()
40
41 // Create task stream request
42 request := &Lattice.TaskStreamRequest{
43 HeartbeatIntervalMs: Lattice.Int(10000),
44 ExcludePreexistingTasks: Lattice.Bool(false),
45 TaskType: &Lattice.TaskStreamRequestTaskType{
46 TaskStreamRequestTaskTypeTaskTypePrefix: &Lattice.TaskStreamRequestTaskTypeTaskTypePrefix{
47 // Define a prefix filter for tasks that belong to your organization
48 TaskTypePrefix: "type.googleapis.com/<your-organization>.tasks",
49 },
50 },
51 }
52
53 // Start the task stream
54 stream, err := LatticeClient.Tasks.StreamTasks(ctx, request)
55 if err != nil {
56 fmt.Printf("Failed to start task stream: %v\n", err)
57 return
58 }
59 defer stream.Close()
60
61 // Process the stream events as they arrive
62 for {
63 event, err := stream.Recv()
64 if err != nil {
65 fmt.Printf("Error receiving task stream event: %v\n", err)
66 return
67 }
68
69 if event.Event == "heartbeat" {
70 // Process heartbeat events
71 timestamp := *event.GetHeartbeat().GetTimestamp()
72 fmt.Printf("Heartbeat: %v\n", timestamp)
73 } else {
74 task := event.GetTaskEvent().GetTaskEvent().GetTask()
75 taskId := task.GetVersion().GetTaskID()
76 status := task.GetStatus().GetStatus()
77
78 fmt.Printf(" TaskID: %v\n", *taskId)
79 fmt.Printf(" Status: %v\n", *status)
80 }
81 }
82}

The stream produces two main event types:

  • heartbeat: Regular server heartbeats that confirm the connection is active.
  • task_event: Notifications about task creation, updates, or status changes.
3

Verify the results

When successfully running the StreamTasks code, you’ll see output similar to the following:

$Starting task stream
$Heartbeat: 2025-01-29T12:34:56.789Z
$Task Event: TASK_CREATED
$ TaskID: task-123456
$ Status: STATUS_SENT
$Task Event: TASK_UPDATED
$ TaskID: task-123456
$ Status: STATUS_MACHINE_RECEIPT
$Heartbeat: 2025-01-29T12:35:06.789Z
$Task Event: TASK_UPDATED
$ TaskID: task-123456
$ Status: STATUS_EXECUTING

Cancel tasks

The CancelTask API cancels a task by marking it for cancellation in the system. The behavior depends on the task’s current state:

  • If the task has not been sent to an agent, it cancels immediately and transitions to a terminal state (STATUS_DONE_NOT_OK with ERROR_CODE_CANCELLED).
  • If the task has been sent to an agent, the cancellation request is routed to the agent, which decides whether to accept or reject the cancellation.

This section covers the operator’s side of cancellation: sending the request and reading the result. The agent that receives the routed request must set up a task processor and handle cancellation requests — see Handle task cancellation in the agent integration guide.

1

Request task cancellation

Send a task cancellation request to Lattice. If the task has already been sent to an agent, Lattice routes the request to the agent:

1package main
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8
9 Lattice "github.com/anduril/lattice-sdk-go/v4"
10 "github.com/anduril/lattice-sdk-go/v4/client"
11 "github.com/anduril/lattice-sdk-go/v4/option"
12)
13
14func main() {
15 // Get environment variables
16 latticeEndpoint := os.Getenv("LATTICE_ENDPOINT")
17 clientSecret := os.Getenv("LATTICE_CLIENT_SECRET")
18 clientId := os.Getenv("LATTICE_CLIENT_ID")
19 sandboxesToken := os.Getenv("SANDBOXES_TOKEN")
20
21 // Check required environment variables
22 if latticeEndpoint == "" || clientId == "" || clientSecret == "" || sandboxesToken == "" {
23 fmt.Println("Missing required environment variables.")
24 os.Exit(1)
25 }
26
27 // Check for required command-line arguments
28 if len(os.Args) < 2 {
29 fmt.Println("Usage: go run cancel_task.go <task_id> [entity_id]")
30 os.Exit(1)
31 }
32
33 taskId := os.Args[1]
34 var entityId *string
35 if len(os.Args) > 2 {
36 entityId = &os.Args[2]
37 }
38
39 // Initialize headers for sandbox authorization
40 headers := http.Header{}
41 headers.Add("Anduril-Sandbox-Authorization", fmt.Sprintf("Bearer %s", sandboxesToken))
42
43 // Create the client
44 LatticeClient := client.NewClient(
45 option.WithClientCredentials(clientId, clientSecret),
46 option.WithBaseURL(fmt.Sprintf("https://%s", latticeEndpoint)),
47 option.WithHTTPHeader(headers),
48 )
49
50 // Create context for the request
51 ctx := context.Background()
52
53 // Build author based on whether entity_id is provided
54 var author *Lattice.Principal
55 if entityId != nil {
56 author = &Lattice.Principal{
57 System: &Lattice.System{
58 EntityID: entityId,
59 },
60 }
61 fmt.Printf("Cancelling task: %s\n", taskId)
62 fmt.Printf("Author entity: %s\n", *entityId)
63 } else {
64 userId := "operator_1"
65 author = &Lattice.Principal{
66 User: &Lattice.User{
67 UserID: &userId,
68 },
69 }
70 fmt.Printf("Cancelling task: %s\n", taskId)
71 }
72
73 // Create cancel task request
74 cancelRequest := Lattice.TaskCancellation{
75 TaskID: taskId,
76 Author: author,
77 }
78
79 // Call the CancelTask API
80 _, err := LatticeClient.Tasks.CancelTask(ctx, &cancelRequest)
81 if err != nil {
82 fmt.Printf("Error cancelling task: %v\n", err)
83 os.Exit(1)
84 }
85
86 fmt.Printf("Cancel task response: {}\n")
87}

The CancelTask API accepts the following parameters:

taskId
StringRequired

The unique identifier of the task to cancel.

author
PrincipalRequired

Who or what is requesting the cancellation. Can be a user Principal with a userId or a system Principal with an entityId.

Run the cancellation request with the ID of the task you want to cancel:

$python cancel_task.py <task_id>
2

Verify the results

After requesting cancellation, the task will transition to one of two outcomes:

Successful cancellation:

When the agent accepts the cancellation, the task transitions to STATUS_DONE_NOT_OK with ERROR_CODE_CANCELLED:

$Cancelling task: task-123456
$Task has been cancelled.
$Task Status: STATUS_DONE_NOT_OK
$Error Code: ERROR_CODE_CANCELLED
$Error Message: Task cancelled by agent.

Rejected cancellation:

When the agent rejects the cancellation, the task retains its current status but includes a TaskError with ERROR_CODE_REJECTED:

$Cancelling task: task-123456
$Task could not be cancelled.
$Task Status: STATUS_EXECUTING
$Error Code: ERROR_CODE_REJECTED
$Error Message: Task is already active, and cannot be cancelled.

For more information, see CancelTask in the Lattice API Reference.

What’s next