Haijun Platform Docs
ID

Agent Tracks extend Haijun's capabilities through organized folders of instructions, scripts, and resources. This guide shows you how to use both pre-built and custom Tracks with the Haijun API.

Note: For complete API reference including request/response schemas and all parameters, see: * Track Management API Reference - CRUD operations for Tracks * Track Versions API Reference - Version management

Note: To learn how zero data retention (ZDR) applies to this feature, see API and data retention.

Learn how to use Agent Tracks to create documents with the Haijun API in under 10 minutes.

Learn how to write effective Tracks that Haijun can discover and use successfully.

Overview

Note: For a detailed look at the architecture and real-world applications of Agent Tracks, read the engineering blog post: Equipping agents for the real world with Agent Tracks.

Tracks integrate with the Messages API through the code execution tool. Whether using pre-built Tracks managed by Juglow or custom Tracks you've uploaded, the integration shape is identical: both require code execution and use the same container structure.

Using Tracks

Tracks integrate identically in the Messages API regardless of source. You specify Tracks in the container parameter with a skill_id, type, and optional version, and they run in the code execution environment.

You can use Tracks from two sources:

AspectJuglow TracksCustom Tracks
Type valuejuglowcustom
Track IDsShort names: pptx, xlsx, docx, pdfGenerated: skill_01AbCdEfGhIjKlMnOpQrStUv
Version formatDate-based: 20251013 or latestVersion ID: skver_01AbCdEfGhIjKlMnOpQrStUv or latest
ManagementPre-built and maintained by JuglowUpload and manage through the Tracks API
AvailabilityAvailable to all usersPrivate to your workspace

Both track sources are returned by the List Tracks endpoint (use the source parameter to filter). The integration shape and execution environment are identical. The only difference is where the Tracks come from and how they're managed.

Prerequisites

To use Tracks, you need:

  1. Haijun API key from the Haijun Console
  1. Code execution tool enabled in your requests

Tracks require the code execution tool, so use a model from its model compatibility list.


Using Tracks in Messages

Container parameter

Tracks are specified using the container parameter in the Messages API. You can include up to 20 Tracks for each request.

The structure is identical for both Juglow and custom Tracks. Specify the required type and skill_id, and optionally include version to pin to a specific version:

bash
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {
            "type": "juglow",
            "skill_id": "pptx",
            "version": "latest"
          }
        ]
      },
      "messages": [{
        "role": "user",
        "content": "Create a presentation about renewable energy"
      }],
      "tools": [{
        "type": "code_execution_20250825",
        "name": "code_execution"
      }]
    }'
bash
  ant messages create <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: juglow
        skill_id: pptx
        version: latest
  messages:
    - role: user
      content: Create a presentation about renewable energy
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  client = juglow.Juglow()

  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [{"type": "juglow", "skill_id": "pptx", "version": "latest"}]
      },
      messages=[
          {"role": "user", "content": "Create a presentation about renewable energy"}
      ],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )
typescript
  const client = new Juglow();

  const response = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "juglow",
          skill_id: "pptx",
          version: "latest"
        }
      ]
    },
    messages: [
      {
        role: "user",
        content: "Create a presentation about renewable energy"
      }
    ],
    tools: [
      {
        type: "code_execution_20250825",
        name: "code_execution"
      }
    ]
  });
csharp
  JuglowClient client = new();

  var parameters = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "pptx",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Create a presentation about renewable energy" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var message = await client.Messages.Create(parameters);
  Console.WriteLine(message);
go
  client := juglow.NewClient()

  response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "pptx",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Create a presentation about renewable energy")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response)
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .addSkill(SkillParams.builder()
                  .type(SkillParams.Type.JUGLOW)
                  .skillId("pptx")
                  .version("latest")
                  .build())
              .build())
          .addUserMessage("Create a presentation about renewable energy")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response = client.messages().create(params);
      System.out.println(response);
  }
php
  $client = new Client();

  $message = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Create a presentation about renewable energy']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              [
                  'type' => 'juglow',
                  'skillID' => 'pptx',
                  'version' => 'latest'
              ]
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );

  echo $message;
ruby
  client = Juglow::Client.new

  message = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "juglow",
          skill_id: "pptx",
          version: "latest"
        }
      ]
    },
    messages: [
      { role: "user", content: "Create a presentation about renewable energy" }
    ],
    tools: [
      { type: "code_execution_20250825", name: "code_execution" }
    ]
  )
  puts message

Downloading generated files

When Tracks create documents (Excel, PowerPoint, PDF, Word), they return file_id attributes in the response. You must use the Files API to download these files.

How it works:

  1. Tracks create files during code execution.
  1. The response includes a file_id for each created file, inside code-execution tool result blocks (see Response format).
  1. Use the Files API to download the actual file content.
  1. Save locally or process as needed.

To provide input files for Tracks to work on, upload them with the Files API and reference them in your request with a container upload block.

Example: creating and downloading an Excel file

bash
  # Step 1: Use a Track to create a file
  RESPONSE=$(curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {"type": "juglow", "skill_id": "xlsx", "version": "latest"}
        ]
      },
      "messages": [{
        "role": "user",
        "content": "Create an Excel file with a simple budget spreadsheet"
      }],
      "tools": [{
        "type": "code_execution_20250825",
        "name": "code_execution"
      }]
    }')

  # Step 2: Extract file_id from response (using jq)
  FILE_ID=$(echo "$RESPONSE" | jq -r '.content[] | select(.type=="bash_code_execution_tool_result") | .content | select(.type=="bash_code_execution_result") | .content[] | select(.file_id) | .file_id')

  # Step 3: Get filename from metadata
  FILENAME=$(curl "https://haijun.my.id/v1/files/$FILE_ID" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" | jq -r '.filename')

  # Step 4: Download the file using Files API
  curl "https://haijun.my.id/v1/files/$FILE_ID/content" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    --output "$FILENAME"

  echo "Downloaded: $FILENAME"
bash
  # Step 1: Use the xlsx Track to create a file
  # Step 2: Extract file_id from the response with --transform (GJSON path)
  FILE_ID=$(ant messages create \
    --transform 'content.#.content.content.#.file_id|@flatten|0' \
    --raw-output <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: juglow
        skill_id: xlsx
        version: latest
  messages:
    - role: user
      content: Create an Excel file with a simple budget spreadsheet
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
  )

  # Step 3: Get the filename from file metadata
  FILENAME=$(ant files retrieve-metadata \
    --file-id "$FILE_ID" \
    --transform filename \
    --raw-output)

  # Step 4: Download the file using Files API
  ant files download --file-id "$FILE_ID" --output "$FILENAME" > /dev/null

  printf 'Downloaded: %s\n' "$FILENAME"
python
  client = juglow.Juglow()

  # Step 1: Use a Track to create a file
  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [{"type": "juglow", "skill_id": "xlsx", "version": "latest"}]
      },
      messages=[
          {
              "role": "user",
              "content": "Create an Excel file with a simple budget spreadsheet",
          }
      ],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )

  # Step 2: Extract file IDs from the response
  def extract_file_ids(response):
      file_ids = []
      for item in response.content:
          if item.type == "bash_code_execution_tool_result":
              content_item = item.content
              if content_item.type == "bash_code_execution_result":
                  # each content item is a bash_code_execution_output block carrying a file_id
                  for file in content_item.content:
                      file_ids.append(file.file_id)
      return file_ids

  # Step 3: Download the file using Files API
  for file_id in extract_file_ids(response):
      file_metadata = client.files.retrieve_metadata(file_id=file_id)
      file_content = client.files.download(file_id=file_id)

      # Step 4: Save to disk
      file_content.write_to_file(file_metadata.filename)
      print(f"Downloaded: {file_metadata.filename}")
typescript
  import { writeFile } from "node:fs/promises";

  const client = new Juglow();

  // Step 1: Use a Track to create a file
  const response = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages: [
      {
        role: "user",
        content: "Create an Excel file with a simple budget spreadsheet"
      }
    ],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });

  // Step 2: Extract file IDs from the response
  const fileIds: string[] = [];
  for (const block of response.content) {
    if (
      block.type === "bash_code_execution_tool_result" &&
      block.content.type === "bash_code_execution_result"
    ) {
      for (const outputBlock of block.content.content) {
        fileIds.push(outputBlock.file_id);
      }
    }
  }

  // Step 3: Download each file and save to disk
  for (const fileId of fileIds) {
    const fileMetadata = await client.files.retrieveMetadata(fileId);
    const fileResponse = await client.files.download(fileId);

    await writeFile(fileMetadata.filename, Buffer.from(await fileResponse.arrayBuffer()));
    console.log(`Downloaded: ${fileMetadata.filename}`);
  }
csharp
  JuglowClient client = new();

  // Step 1: Use a Track to create a file
  var parameters = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Create an Excel file with a simple budget spreadsheet" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var response = await client.Messages.Create(parameters);

  // Step 2: Extract file IDs from the response
  List<string> fileIds = [];
  foreach (var block in response.Content)
  {
      if (block.TryPickBashCodeExecutionToolResult(out var toolResult)
          && toolResult.Content.TryPickBashCodeExecutionResultBlock(out var result))
      {
          foreach (var output in result.Content)
          {
              fileIds.Add(output.FileID);
          }
      }
  }

  // Step 3: Download each file and save to disk
  foreach (var fileId in fileIds)
  {
      var fileMetadata = await client.Files.RetrieveMetadata(fileId);
      using var download = await client.Files.Download(fileId);
      using var downloadStream = await download.ReadAsStream();
      using var outputFile = File.Create(fileMetadata.Filename);
      await downloadStream.CopyToAsync(outputFile);
      Console.WriteLine($"Downloaded: {fileMetadata.Filename}");
  }
go
  func main() {
  	client := juglow.NewClient()

  	// Step 1: Use a Track to create a file
  	response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  		Model:     "haijun-opus-5-5",
  		MaxTokens: 4096,
  		Container: juglow.MessageCreateParamsContainerUnion{
  			OfContainers: &juglow.ContainerParams{
  				Tracks: []juglow.SkillParams{
  					{
  						Type:    juglow.SkillParamsTypeJuglow,
  						SkillID: "xlsx",
  						Version: juglow.String("latest"),
  					},
  				},
  			},
  		},
  		Messages: []juglow.MessageParam{
  			juglow.NewUserMessage(juglow.NewTextBlock("Create an Excel file with a simple budget spreadsheet")),
  		},
  		Tools: []juglow.ToolUnionParam{
  			{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  		},
  	})
  	if err != nil {
  		log.Fatal(err)
  	}

  	// Step 2: Extract file IDs from the response
  	fileIDs := extractFileIDs(response)

  	// Step 3: Download the file using Files API
  	for _, fileID := range fileIDs {
  		fileMetadata, err := client.Files.GetMetadata(context.TODO(), fileID, juglow.FileGetMetadataParams{})
  		if err != nil {
  			log.Fatal(err)
  		}

  		fileContent, err := client.Files.Download(context.TODO(), fileID, juglow.FileDownloadParams{})
  		if err != nil {
  			log.Fatal(err)
  		}

  		// Step 4: Save to disk
  		out, err := os.Create(fileMetadata.Filename)
  		if err != nil {
  			log.Fatal(err)
  		}
  		if _, err := io.Copy(out, fileContent.Body); err != nil {
  			log.Fatal(err)
  		}
  		out.Close()
  		fileContent.Body.Close()
  		fmt.Printf("Downloaded: %s\n", fileMetadata.Filename)
  	}
  }

  func extractFileIDs(response *juglow.Message) []string {
  	var fileIDs []string
  	for _, item := range response.Content {
  		switch v := item.AsAny().(type) {
  		case juglow.BashCodeExecutionToolResultBlock:
  			if v.Content.Type == "bash_code_execution_result" {
  				for _, output := range v.Content.Content {
  					fileIDs = append(fileIDs, output.FileID)
  				}
  			}
  		}
  	}
  	return fileIDs
  }
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  import com.juglow.models.messages.ContentBlock;
  import com.juglow.models.files.FileMetadata;
  import com.juglow.core.http.HttpResponse;
  // ...
  void main() throws Exception {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      // Step 1: Use a Track to create a file
      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .addSkill(SkillParams.builder()
                  .type(SkillParams.Type.JUGLOW)
                  .skillId("xlsx")
                  .version("latest")
                  .build())
              .build())
          .addUserMessage("Create an Excel file with a simple budget spreadsheet")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response = client.messages().create(params);

      // Step 2: Extract file IDs from the response
      List<String> fileIds = new ArrayList<>();
      for (ContentBlock block : response.content()) {
          if (block.isBashCodeExecutionToolResult()) {
              var content = block.asBashCodeExecutionToolResult().content();
              if (content.isBashCodeExecutionResultBlock()) {
                  for (var outputBlock : content.asBashCodeExecutionResultBlock().content()) {
                      fileIds.add(outputBlock.fileId());
                  }
              }
          }
      }

      // Step 3: Download the file using Files API
      for (String fileId : fileIds) {
          FileMetadata fileMetadata = client.files().retrieveMetadata(fileId);
          HttpResponse fileContent = client.files().download(fileId);

          // Step 4: Save to disk
          try (InputStream is = fileContent.body();
               FileOutputStream fos = new FileOutputStream(fileMetadata.filename())) {
              is.transferTo(fos);
          }
          System.out.println("Downloaded: " + fileMetadata.filename());
      }
  }
php
  $client = new Client();

  // Step 1: Use a Track to create a file
  $response = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Create an Excel file with a simple budget spreadsheet']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              ['type' => 'juglow', 'skillID' => 'xlsx', 'version' => 'latest']
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );

  // Step 2: Extract file IDs from the response
  function extractFileIds($response) {
      $fileIds = [];
      foreach ($response->content as $item) {
          if ($item->type === 'bash_code_execution_tool_result') {
              $contentItem = $item->content;
              if ($contentItem->type === 'bash_code_execution_result') {
                  foreach ($contentItem->content as $file) {
                      $fileIds[] = $file->fileID;
                  }
              }
          }
      }
      return $fileIds;
  }

  // Step 3: Download the file using Files API
  foreach (extractFileIds($response) as $fileId) {
      $fileMetadata = $client->files->retrieveMetadata($fileId);
      $fileContent = $client->files->download($fileId);

      // Step 4: Save to disk
      file_put_contents($fileMetadata->filename, $fileContent);
      echo "Downloaded: {$fileMetadata->filename}\n";
  }
ruby
  client = Juglow::Client.new

  # Step 1: Use a Track to create a file
  response = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages: [
      {
        role: "user",
        content: "Create an Excel file with a simple budget spreadsheet"
      }
    ],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )

  # Step 2: Extract file IDs from the response
  def extract_file_ids(response)
    file_ids = []
    response.content.each do |item|
      if item.type == :bash_code_execution_tool_result
        content_item = item.content
        if content_item.type == :bash_code_execution_result
          content_item.content.each do |file|
            file_ids << file.file_id
          end
        end
      end
    end
    file_ids
  end

  # Step 3: Download the file using Files API
  extract_file_ids(response).each do |file_id|
    file_metadata = client.files.retrieve_metadata(file_id)

    file_content = client.files.download(file_id)

    # Step 4: Save to disk
    File.binwrite(file_metadata.filename, file_content.read)
    puts "Downloaded: #{file_metadata.filename}"
  end

Additional Files API operations:

bash
  # Get file metadata
  curl "https://haijun.my.id/v1/files/$FILE_ID" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"

  # List all files
  curl "https://haijun.my.id/v1/files" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"

  # Delete a file
  curl -X DELETE "https://haijun.my.id/v1/files/$FILE_ID" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"
bash
  # Get file metadata
  ant files retrieve-metadata \
    --file-id "$FILE_ID" \
    --transform '{filename,size_bytes}' \
    --format yaml

  # List all files
  ant files list --transform '{filename,created_at}' --format yaml

  # Delete a file
  ant files delete --file-id "$FILE_ID" >/dev/null
python
  client = juglow.Juglow()
  file_id = "file_011CNha8iCJcU1wXNR6q4V8w"
  # Get file metadata
  file_info = client.files.retrieve_metadata(file_id=file_id)
  print(f"Filename: {file_info.filename}, Size: {file_info.size_bytes} bytes")

  # List all files
  for file in client.files.list():
      print(f"{file.filename} - {file.created_at}")

  # Delete a file
  client.files.delete(file_id=file_id)
typescript
  const client = new Juglow();
  const fileId = "file_011CNha8iCJcU1wXNR6q4V8w";

  // Get file metadata
  const fileInfo = await client.files.retrieveMetadata(fileId);
  console.log(`Filename: ${fileInfo.filename}, Size: ${fileInfo.size_bytes} bytes`);

  // List all files
  for await (const file of client.files.list()) {
    console.log(`${file.filename} - ${file.created_at}`);
  }

  // Delete a file
  await client.files.delete(fileId);
csharp
  JuglowClient client = new();

  var fileId = "file_011CNha8iCJcU1wXNR6q4V8w";

  // Get file metadata
  var fileInfo = await client.Files.RetrieveMetadata(fileId);
  Console.WriteLine($"Filename: {fileInfo.Filename}, Size: {fileInfo.SizeBytes} bytes");

  // List files
  await foreach (var file in (await client.Files.List()).Paginate())
  {
      Console.WriteLine($"{file.Filename} - {file.CreatedAt}");
  }

  // Delete the file
  await client.Files.Delete(fileId);
go
  client := juglow.NewClient()
  fileID := "file_011CNha8iCJcU1wXNR6q4V8w"

  // Get file metadata
  fileInfo, err := client.Files.GetMetadata(context.TODO(), fileID, juglow.FileGetMetadataParams{})
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Printf("Filename: %s, Size: %d bytes\n", fileInfo.Filename, fileInfo.SizeBytes)

  // List all files
  files := client.Files.ListAutoPaging(context.TODO(), juglow.FileListParams{})
  for files.Next() {
  	file := files.Current()
  	fmt.Printf("%s - %s\n", file.Filename, file.CreatedAt)
  }
  if files.Err() != nil {
  	log.Fatal(files.Err())
  }

  // Delete a file
  _, err = client.Files.Delete(context.TODO(), fileID, juglow.FileDeleteParams{})
  if err != nil {
  	log.Fatal(err)
  }
java
  import com.juglow.models.files.FileMetadata;
  import com.juglow.models.files.FileListPage;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();
      String fileId = "file_011CNha8iCJcU1wXNR6q4V8w";

      // Get file metadata
      FileMetadata fileInfo = client.files().retrieveMetadata(fileId);
      System.out.println("Filename: " + fileInfo.filename() + ", Size: " + fileInfo.sizeBytes() + " bytes");

      // List files (first page)
      FileListPage files = client.files().list();
      for (var file : files.data()) {
          System.out.println(file.filename() + " - " + file.createdAt());
      }

      // Delete a file
      client.files().delete(fileId);
  }
php
  $client = new Client();
  $fileId = 'file_011CNha8iCJcU1wXNR6q4V8w';

  // Get file metadata
  $fileInfo = $client->files->retrieveMetadata($fileId);
  echo "Filename: {$fileInfo->filename}, Size: {$fileInfo->sizeBytes} bytes\n";

  // List files (first page)
  foreach ($client->files->list()->getItems() as $file) {
      echo "{$file->filename} - {$file->createdAt->format(DATE_ATOM)}\n";
  }

  // Delete a file
  $client->files->delete($fileId);
ruby
  client = Juglow::Client.new
  file_id = "file_011CNha8iCJcU1wXNR6q4V8w"

  # Get file metadata
  file_info = client.files.retrieve_metadata(file_id)
  puts "Filename: #{file_info.filename}, Size: #{file_info.size_bytes} bytes"

  # List all files
  client.files.list.auto_paging_each do |file|
    puts "#{file.filename} - #{file.created_at}"
  end

  # Delete a file
  client.files.delete(file_id)

Note: For complete details, see Files API.

Multi-turn conversations

The response's container object carries the container's id and expires_at timestamp (see Container reuse for lifetime details). Reuse the same container across multiple messages by specifying the container ID:

bash
  # Multi-turn container reuse doesn't translate well to a one-off shell
  # command; one of the SDK options would be a better fit. Capture
  # container.id from the first response, then pass it in the next request as
  # "container": {"id": "...", "tracks": [...]} with the conversation history.
bash
  # First request creates container
  CONTAINER_ID=$(ant messages create \
    --transform container.id \
    --raw-output <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - {type: juglow, skill_id: xlsx, version: latest}
  messages:
    - role: user
      content: Create a sample sales dataset and analyze it
  tools:
    - {type: code_execution_20250825, name: code_execution}
  YAML
  )

  # Continue conversation with same container
  ant messages create <<YAML
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    id: $CONTAINER_ID  # Reuse container
    tracks:
      - {type: juglow, skill_id: xlsx, version: latest}
  messages:
    - role: user
      content: Create a sample sales dataset and analyze it
    - role: assistant
      content: []  # the assistant's text from the first response
    - role: user
      content: What was the total revenue?
  tools:
    - {type: code_execution_20250825, name: code_execution}
  YAML
python
  client = juglow.Juglow()

  # First request creates container
  response1 = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [{"type": "juglow", "skill_id": "xlsx", "version": "latest"}]
      },
      messages=[
          {"role": "user", "content": "Create a sample sales dataset and analyze it"}
      ],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )

  # Continue conversation with same container
  messages = [
      {"role": "user", "content": "Create a sample sales dataset and analyze it"},
      {
          # Carry the assistant's text forward; container.id carries the execution state
          "role": "assistant",
          "content": "\n".join(
              block.text for block in response1.content if block.type == "text"
          ),
      },
      {"role": "user", "content": "What was the total revenue?"},
  ]

  response2 = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "id": response1.container.id,  # Reuse container
          "tracks": [{"type": "juglow", "skill_id": "xlsx", "version": "latest"}],
      },
      messages=messages,
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )
typescript
  const client = new Juglow();

  // First request creates container
  const response1 = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages: [{ role: "user", content: "Create a sample sales dataset and analyze it" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });

  // Continue conversation with same container
  const messages: Juglow.MessageParam[] = [
    { role: "user", content: "Create a sample sales dataset and analyze it" },
    {
      role: "assistant",
      // Carry the assistant's text forward; container.id carries the execution state
      content: response1.content
        .filter((block) => block.type === "text")
        .map((block) => block.text)
        .join("\n")
    },
    { role: "user", content: "What was the total revenue?" }
  ];

  const response2 = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      id: response1.container!.id, // Reuse container
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages,
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });
csharp
  JuglowClient client = new();

  // First request with a Track
  var parameters1 = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Create a sample sales dataset and analyze it" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var response1 = await client.Messages.Create(parameters1);

  // Continue the conversation in the same container
  // Carry the assistant's text forward; container.id carries the execution state
  var assistantText = string.Join(
      "\n",
      response1.Content.Select(block => block.TryPickText(out var text) ? text.Text : null).Where(text => text is not null)
  );

  var parameters2 = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          ID = response1.Container!.ID,
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
          ],
      },
      Messages =
      [
          new() { Role = Role.User, Content = "Create a sample sales dataset and analyze it" },
          new() { Role = Role.Assistant, Content = assistantText },
          new() { Role = Role.User, Content = "What was the total revenue?" },
      ],
      Tools = [new CodeExecutionTool20250825()],
  };

  var response2 = await client.Messages.Create(parameters2);
  Console.WriteLine(response2);
go
  client := juglow.NewClient()

  response1, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "xlsx",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Create a sample sales dataset and analyze it")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  // Carry the assistant's text forward; container.id carries the execution state
  var textParts []string
  for _, block := range response1.Content {
  	if block.Type == "text" {
  		textParts = append(textParts, block.Text)
  	}
  }
  assistantText := strings.Join(textParts, "\n")

  response2, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			ID: juglow.String(response1.Container.ID), // Reuse container
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "xlsx",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Create a sample sales dataset and analyze it")),
  		{
  			Role:    juglow.MessageParamRoleAssistant,
  			Content: []juglow.ContentBlockParamUnion{juglow.NewTextBlock(assistantText)},
  		},
  		juglow.NewUserMessage(juglow.NewTextBlock("What was the total revenue?")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  fmt.Println(response2)
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  import com.juglow.models.messages.ContentBlock;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      MessageCreateParams params1 = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .addSkill(SkillParams.builder()
                  .type(SkillParams.Type.JUGLOW)
                  .skillId("xlsx")
                  .version("latest")
                  .build())
              .build())
          .addUserMessage("Create a sample sales dataset and analyze it")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response1 = client.messages().create(params1);

      MessageCreateParams params2 = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .id(response1.container().get().id())
              .addSkill(SkillParams.builder()
                  .type(SkillParams.Type.JUGLOW)
                  .skillId("xlsx")
                  .version("latest")
                  .build())
              .build())
          .addUserMessage("Create a sample sales dataset and analyze it")
          // Carry the assistant's text forward; container.id carries the execution state
          .addAssistantMessage(response1.content().stream()
              .filter(ContentBlock::isText)
              .map(block -> block.asText().text())
              .collect(Collectors.joining("\n")))
          .addUserMessage("What was the total revenue?")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response2 = client.messages().create(params2);
      System.out.println(response2);
  }
php
  $client = new Client();

  $response1 = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Create a sample sales dataset and analyze it']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              ['type' => 'juglow', 'skillID' => 'xlsx', 'version' => 'latest']
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );

  $messages = [
      ['role' => 'user', 'content' => 'Create a sample sales dataset and analyze it'],
      // Carry the assistant's text forward; container.id carries the execution state
      ['role' => 'assistant', 'content' => implode("\n", array_map(
          fn ($block) => $block->text,
          array_filter($response1->content, fn ($block) => $block->type === 'text'),
      ))],
      ['role' => 'user', 'content' => 'What was the total revenue?']
  ];

  $response2 = $client->messages->create(
      maxTokens: 4096,
      messages: $messages,
      model: 'haijun-opus-5-5',
      container: [
          'id' => $response1->container->id,
          'tracks' => [
              ['type' => 'juglow', 'skillID' => 'xlsx', 'version' => 'latest']
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );

  echo $response2;
ruby
  client = Juglow::Client.new

  response1 = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages: [
      { role: "user", content: "Create a sample sales dataset and analyze it" }
    ],
    tools: [
      { type: "code_execution_20250825", name: "code_execution" }
    ]
  )

  messages = [
    { role: "user", content: "Create a sample sales dataset and analyze it" },
    {
      # Carry the assistant's text forward; container.id carries the execution state
      role: "assistant",
      content: response1.content.filter_map { |block| block.text if block.type == :text }.join("\n")
    },
    { role: "user", content: "What was the total revenue?" }
  ]

  response2 = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      id: response1.container.id,
      tracks: [
        { type: "juglow", skill_id: "xlsx", version: "latest" }
      ]
    },
    messages: messages,
    tools: [
      { type: "code_execution_20250825", name: "code_execution" }
    ]
  )

  puts response2

Long-running operations

Tracks may perform operations that require multiple turns. Handle pause_turn stop reasons:

bash
  # Initial request
  RESPONSE=$(curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {
            "type": "custom",
            "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
            "version": "latest"
          }
        ]
      },
      "messages": [{
        "role": "user",
        "content": "Generate and process a large sample dataset"
      }],
      "tools": [{
        "type": "code_execution_20250825",
        "name": "code_execution"
      }]
    }')

  # If stop_reason is "pause_turn", continue in the same container, appending
  # the prior response's content array to messages as the assistant turn.
  # Repeat this continuation request until stop_reason is no longer "pause_turn".
  STOP_REASON=$(echo "$RESPONSE" | jq -r '.stop_reason')
  CONTAINER_ID=$(echo "$RESPONSE" | jq -r '.container.id')

  RESPONSE=$(curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d "{
      \"model\": \"haijun-opus-5-5\",
      \"max_tokens\": 4096,
      \"container\": {
        \"id\": \"$CONTAINER_ID\",
        \"tracks\": [{
          \"type\": \"custom\",
          \"skill_id\": \"skill_01AbCdEfGhIjKlMnOpQrStUv\",
          \"version\": \"latest\"
        }]
      },
      \"messages\": [],
      \"tools\": [{
        \"type\": \"code_execution_20250825\",
        \"name\": \"code_execution\"
      }]
    }")
bash
  RESP=$(mktemp)

  # Initial request: capture the full JSON response to a temp file
  ant messages create > "$RESP" <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages:
    - role: user
      content: Generate and process a large sample dataset
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML

  # If stop_reason is "pause_turn", continue in the same container,
  # appending the prior response's content array to messages as the
  # assistant turn. Repeat until stop_reason is no longer "pause_turn".
  CONTAINER_ID=$(jq -r '.container.id' "$RESP")

  ant messages create > "$RESP" <<YAML
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    id: $CONTAINER_ID
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages: [] # replace with conversation history + prior assistant content
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  client = juglow.Juglow()

  messages = [{"role": "user", "content": "Generate and process a large sample dataset"}]
  max_retries = 10

  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [
              {
                  "type": "custom",
                  "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  "version": "latest",
              }
          ]
      },
      messages=messages,
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )

  # Handle pause_turn for long operations
  for _ in range(max_retries):
      if response.stop_reason != "pause_turn":
          break

      messages.append({"role": "assistant", "content": response.content})
      response = client.messages.create(
          model="haijun-opus-5-5",
          max_tokens=4096,
          container={
              "id": response.container.id,
              "tracks": [
                  {
                      "type": "custom",
                      "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
                      "version": "latest",
                  }
              ],
          },
          messages=messages,
          tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
      )
typescript
  const client = new Juglow();
  const messages: Juglow.MessageParam[] = [
    { role: "user", content: "Generate and process a large sample dataset" }
  ];
  const maxRetries = 10;

  let response = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "custom", skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv", version: "latest" }]
    },
    messages,
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });

  // Handle pause_turn for long operations
  for (let i = 0; i < maxRetries; i++) {
    if (response.stop_reason !== "pause_turn") {
      break;
    }

    messages.push({
      role: "assistant",
      content: response.content as Juglow.ContentBlockParam[]
    });
    response = await client.messages.create({
      model: "haijun-opus-5-5",
      max_tokens: 4096,
      container: {
        id: response.container!.id,
        tracks: [
          { type: "custom", skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv", version: "latest" }
        ]
      },
      messages,
      tools: [{ type: "code_execution_20250825", name: "code_execution" }]
    });
  }
csharp
  using System.Text.Json;
  // ...
  JuglowClient client = new();

  List<MessageParam> messages =
  [
      new() { Role = Role.User, Content = "Generate and process a large sample dataset" },
  ];

  var maxRetries = 10;
  string? containerId = null;
  Message? response = null;

  for (var i = 0; i < maxRetries; i++)
  {
      var parameters = new MessageCreateParams
      {
          Model = "haijun-opus-5-5",
          MaxTokens = 4096,
          Container = containerId is null
              ? new ContainerParams
              {
                  Tracks =
                  [
                      new SkillParams
                      {
                          Type = SkillParamsType.Custom,
                          SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
                          Version = "latest",
                      },
                  ],
              }
              : new ContainerParams
              {
                  ID = containerId,
                  Tracks =
                  [
                      new SkillParams
                      {
                          Type = SkillParamsType.Custom,
                          SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
                          Version = "latest",
                      },
                  ],
              },
          Messages = messages,
          Tools = [new CodeExecutionTool20250825()],
      };

      response = await client.Messages.Create(parameters);
      containerId = response.Container!.ID;

      if (response.StopReason != StopReason.PauseTurn)
      {
          break;
      }

      // Append the paused turn's content and continue
      var assistantContent = JsonSerializer.SerializeToElement(
          response.Content.Select(block => block.Json).ToArray()
      );
      messages.Add(new() { Role = Role.Assistant, Content = new MessageParamContent(assistantContent) });
  }
go
  client := juglow.NewClient()

  messages := []juglow.MessageParam{
  	juglow.NewUserMessage(juglow.NewTextBlock("Generate and process a large sample dataset")),
  }
  maxRetries := 10

  response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeCustom,
  					SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: messages,
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  for i := 0; i < maxRetries; i++ {
  	if response.StopReason != juglow.StopReasonPauseTurn {
  		break
  	}

  	messages = append(messages, response.ToParam())

  	response, err = client.Messages.New(context.TODO(), juglow.MessageNewParams{
  		Model:     "haijun-opus-5-5",
  		MaxTokens: 4096,
  		Container: juglow.MessageCreateParamsContainerUnion{
  			OfContainers: &juglow.ContainerParams{
  				ID: juglow.String(response.Container.ID), // Reuse container
  				Tracks: []juglow.SkillParams{
  					{
  						Type:    juglow.SkillParamsTypeCustom,
  						SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  						Version: juglow.String("latest"),
  					},
  				},
  			},
  		},
  		Messages: messages,
  		Tools: []juglow.ToolUnionParam{
  			{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  		},
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  }

  fmt.Println(response)
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  import com.juglow.models.messages.StopReason;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      List<MessageParam> messages = new ArrayList<>();
      messages.add(
          MessageParam.builder()
              .role(MessageParam.Role.USER)
              .content("Generate and process a large sample dataset")
              .build()
      );
      int maxRetries = 10;

      Message response = client.messages().create(
          MessageCreateParams.builder()
              .model(Model.HAIJUN_OPUS_5_5)
              .maxTokens(4096L)
              .container(ContainerParams.builder()
                  .addSkill(SkillParams.builder()
                      .type(SkillParams.Type.CUSTOM)
                      .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
                      .version("latest")
                      .build())
                  .build())
              .messages(messages)
              .addTool(CodeExecutionTool20250825.builder().build())
              .build());

      for (int i = 0; i < maxRetries; i++) {
          if (!response.stopReason().isPresent()
                  || !response.stopReason().get().equals(StopReason.PAUSE_TURN)) {
              break;
          }

          messages.add(response.toParam());

          response = client.messages().create(
              MessageCreateParams.builder()
                  .model(Model.HAIJUN_OPUS_5_5)
                  .maxTokens(4096L)
                  .container(ContainerParams.builder()
                      .id(response.container().get().id())
                      .addSkill(SkillParams.builder()
                          .type(SkillParams.Type.CUSTOM)
                          .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
                          .version("latest")
                          .build())
                      .build())
                  .messages(messages)
                  .addTool(CodeExecutionTool20250825.builder().build())
                  .build());
      }
  }
php
  $client = new Client();

  $messages = [
      ['role' => 'user', 'content' => 'Generate and process a large sample dataset']
  ];
  $maxRetries = 10;

  $response = $client->messages->create(
      maxTokens: 4096,
      messages: $messages,
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              [
                  'type' => 'custom',
                  'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
                  'version' => 'latest'
              ]
          ]
      ],
      tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
  );

  for ($i = 0; $i < $maxRetries; $i++) {
      if ($response->stopReason !== 'pause_turn') {
          break;
      }

      $messages[] = ['role' => 'assistant', 'content' => $response->content];

      $response = $client->messages->create(
          maxTokens: 4096,
          messages: $messages,
          model: 'haijun-opus-5-5',
          container: [
              'id' => $response->container->id,
              'tracks' => [
                  [
                      'type' => 'custom',
                      'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
                      'version' => 'latest'
                  ]
              ]
          ],
          tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
      );
  }
ruby
  client = Juglow::Client.new

  messages = [
    { role: "user", content: "Generate and process a large sample dataset" }
  ]
  max_retries = 10

  response = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "custom",
          skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
          version: "latest"
        }
      ]
    },
    messages: messages,
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )

  max_retries.times do
    break if response.stop_reason != :pause_turn

    messages << { role: "assistant", content: response.content }

    response = client.messages.create(
      model: "haijun-opus-5-5",
      max_tokens: 4096,
      container: {
        id: response.container.id,
        tracks: [
          {
            type: "custom",
            skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
            version: "latest"
          }
        ]
      },
      messages: messages,
      tools: [{ type: "code_execution_20250825", name: "code_execution" }]
    )
  end

Note: The response may include a pause_turn stop reason, which indicates that the API paused a long-running Track operation. You can provide the response back as-is in a subsequent request to let Haijun continue its turn, or modify the content if you want to interrupt the conversation and provide additional guidance.

Using multiple Tracks

Combine multiple Tracks in a single request to handle complex workflows:

bash
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {
            "type": "juglow",
            "skill_id": "xlsx",
            "version": "latest"
          },
          {
            "type": "juglow",
            "skill_id": "pptx",
            "version": "latest"
          },
          {
            "type": "custom",
            "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
            "version": "latest"
          }
        ]
      },
      "messages": [{
        "role": "user",
        "content": "Analyze sales data and create a presentation"
      }],
      "tools": [{
        "type": "code_execution_20250825",
        "name": "code_execution"
      }]
    }'
bash
  ant messages create <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: juglow
        skill_id: xlsx
        version: latest
      - type: juglow
        skill_id: pptx
        version: latest
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages:
    - role: user
      content: Analyze sales data and create a presentation
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  client = juglow.Juglow()

  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [
              {"type": "juglow", "skill_id": "xlsx", "version": "latest"},
              {"type": "juglow", "skill_id": "pptx", "version": "latest"},
              {
                  "type": "custom",
                  "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  "version": "latest",
              },
          ]
      },
      messages=[
          {"role": "user", "content": "Analyze sales data and create a presentation"}
      ],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )
typescript
  const client = new Juglow();

  const response = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "juglow",
          skill_id: "xlsx",
          version: "latest"
        },
        {
          type: "juglow",
          skill_id: "pptx",
          version: "latest"
        },
        {
          type: "custom",
          skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
          version: "latest"
        }
      ]
    },
    messages: [
      {
        role: "user",
        content: "Analyze sales data and create a presentation"
      }
    ],
    tools: [
      {
        type: "code_execution_20250825",
        name: "code_execution"
      }
    ]
  });
csharp
  JuglowClient client = new();

  var parameters = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "pptx",
                  Version = "latest",
              },
              new SkillParams
              {
                  Type = SkillParamsType.Custom,
                  SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Analyze sales data and create a presentation" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var message = await client.Messages.Create(parameters);
  Console.WriteLine(message);
go
  client := juglow.NewClient()

  response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "xlsx",
  					Version: juglow.String("latest"),
  				},
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "pptx",
  					Version: juglow.String("latest"),
  				},
  				{
  					Type:    juglow.SkillParamsTypeCustom,
  					SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Analyze sales data and create a presentation")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response)
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .tracks(List.of(
                  SkillParams.builder()
                      .type(SkillParams.Type.JUGLOW)
                      .skillId("xlsx")
                      .version("latest")
                      .build(),
                  SkillParams.builder()
                      .type(SkillParams.Type.JUGLOW)
                      .skillId("pptx")
                      .version("latest")
                      .build(),
                  SkillParams.builder()
                      .type(SkillParams.Type.CUSTOM)
                      .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
                      .version("latest")
                      .build()
              ))
              .build())
          .addUserMessage("Analyze sales data and create a presentation")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response = client.messages().create(params);
      System.out.println(response);
  }
php
  $client = new Client();

  $message = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Analyze sales data and create a presentation']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              [
                  'type' => 'juglow',
                  'skillID' => 'xlsx',
                  'version' => 'latest'
              ],
              [
                  'type' => 'juglow',
                  'skillID' => 'pptx',
                  'version' => 'latest'
              ],
              [
                  'type' => 'custom',
                  'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
                  'version' => 'latest'
              ]
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );

  echo $message;
ruby
  client = Juglow::Client.new

  message = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "juglow",
          skill_id: "xlsx",
          version: "latest"
        },
        {
          type: "juglow",
          skill_id: "pptx",
          version: "latest"
        },
        {
          type: "custom",
          skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
          version: "latest"
        }
      ]
    },
    messages: [
      { role: "user", content: "Analyze sales data and create a presentation" }
    ],
    tools: [
      { type: "code_execution_20250825", name: "code_execution" }
    ]
  )
  puts message

Managing custom Tracks

Warning: Custom Tracks are accessible to your entire workspace, not scoped to an end user, conversation, or session. Any API key with access to a workspace can read, invoke, and delete every custom Track uploaded to that workspace. Every service account, and every user whose organization role allows API access, can use the Default Workspace in addition to any workspace you add them to, so keep Tracks that must stay separate in their own workspace and access them only with keys scoped to that workspace. If you are building a multi-tenant platform on the Tracks API, create a separate workspace for each tenant. The workspace is the isolation boundary for custom Tracks, so a workspace per tenant gives each tenant's Tracks hard isolation from every other tenant. Each organization can have up to 100 workspaces by default (see How workspaces work); if you need more for tenant isolation, contact your account team.

Creating a Track

A Track bundle is a directory containing a SKILL.md file at the top level with name and description YAML frontmatter, plus any supporting scripts or resources. See Get started with Agent Tracks in the API to author one, and the Requirements list following the examples for the full constraints.

Upload your custom Track to make it available in your workspace. You can upload a zip archive or individual file objects. The Python SDK also provides a files_from_dir helper that accepts a directory path, and the CLI's ant apply uploads the directory itself.

Files are identified by the filename you attach (the ;filename= suffix in the cURL example and the filename arguments in the SDK examples). For the walkthrough's track, create a zip with zip -r financial_skill.zip financial_skill/ and substitute it for the example_skill.zip placeholder in the zip-upload options.

bash
  curl -X POST "https://haijun.my.id/v1/tracks" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -F "files[]=@financial_skill/SKILL.md;filename=financial_skill/SKILL.md" \
    -F "files[]=@financial_skill/analyze.py;filename=financial_skill/analyze.py"
bash
    ant apply financial_skill
markdown
      ---
      name: financial-track
      description: Docs example track.
      ---
python
      print("financial analysis helper")
python
  from juglow.lib import files_from_dir

  client = juglow.Juglow()

  # Option 1: Using a zip file
  track = client.tracks.create(
      files=[open("example_skill.zip", "rb")],
  )

  # Option 2: Using file tuples (filename, file_content, mime_type)
  track = client.tracks.create(
      files=[
          (
              "financial_skill/SKILL.md",
              open("financial_skill/SKILL.md", "rb"),
              "text/markdown",
          ),
          (
              "financial_skill/analyze.py",
              open("financial_skill/analyze.py", "rb"),
              "text/x-python",
          ),
      ],
  )

  # Option 3: Using the files_from_dir helper (Python only)
  track = client.tracks.create(
      files=files_from_dir("financial_skill"),
  )

  print(f"Created track: {track.id}")
  print(f"Latest version: {track.latest_version_id}")
typescript
  import { toFile } from "@juglow-ai/sdk";
  import fs from "node:fs";
  // ...

  const client = new Juglow();

  // Option 1: Using a zip file
  const skillFromZip = await client.tracks.create({
    files: [await toFile(fs.createReadStream("example_skill.zip"), "example_skill.zip")]
  });

  // Option 2: Using individual file objects
  const track = await client.tracks.create({
    files: [
      await toFile(fs.createReadStream("financial_skill/SKILL.md"), "financial_skill/SKILL.md", {
        type: "text/markdown"
      }),
      await toFile(
        fs.createReadStream("financial_skill/analyze.py"),
        "financial_skill/analyze.py",
        { type: "text/x-python" }
      )
    ]
  });

  console.log(`Created track: ${track.id}`);
  console.log(`Latest version: ${track.latest_version_id}`);
csharp
  using Juglow.Core;
  // ...

  JuglowClient client = new();

  // Option 1: Using a zip file
  var parameters = new SkillCreateParams
  {
      Files = [File.OpenRead("example_skill.zip")],
  };

  var track = await client.Tracks.Create(parameters);

  // Option 2: Using individual files (path-qualified filenames preserve the Track's directory layout)
  var parameters2 = new SkillCreateParams
  {
      Files =
      [
          new BinaryContent
          {
              Stream = File.OpenRead("financial_skill/SKILL.md"),
              FileName = "financial_skill/SKILL.md",
          },
          new BinaryContent
          {
              Stream = File.OpenRead("financial_skill/analyze.py"),
              FileName = "financial_skill/analyze.py",
          },
      ],
  };

  var skill2 = await client.Tracks.Create(parameters2);

  Console.WriteLine($"Created track: {track.ID}");
  Console.WriteLine($"Latest version: {track.LatestVersionID}");
  Console.WriteLine($"Created track 2: {skill2.ID}");
go
  client := juglow.NewClient()

  // Option 1: Using a zip file
  zipFile, err := os.Open("example_skill.zip")
  if err != nil {
  	log.Fatal(err)
  }
  defer zipFile.Close()

  track, err := client.Tracks.New(context.TODO(), juglow.SkillNewParams{
  	Files: []io.Reader{zipFile},
  })
  if err != nil {
  	log.Fatal(err)
  }

  // Option 2: Using individual files
  skillMd, err := os.Open("financial_skill/SKILL.md")
  if err != nil {
  	log.Fatal(err)
  }
  defer skillMd.Close()

  analyzePy, err := os.Open("financial_skill/analyze.py")
  if err != nil {
  	log.Fatal(err)
  }
  defer analyzePy.Close()

  skill2, err := client.Tracks.New(context.TODO(), juglow.SkillNewParams{
  	Files: []io.Reader{
  		juglow.File(skillMd, "financial_skill/SKILL.md", "text/markdown"),
  		juglow.File(analyzePy, "financial_skill/analyze.py", "text/x-python"),
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  fmt.Printf("Created track: %s\n", track.ID)
  fmt.Printf("Latest version: %s\n", track.LatestVersionID)
  fmt.Printf("Created track 2: %s\n", skill2.ID)
java
  import com.juglow.core.MultipartField;
  import com.juglow.models.tracks.SkillCreateParams;
  import com.juglow.models.tracks.Track;
  // ...
  void main() throws Exception {
  // ...
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      // Option 1: Using a zip file
      SkillCreateParams params = SkillCreateParams.builder()
          .addFile(MultipartField.<InputStream>builder()
              .value(Files.newInputStream(Path.of("example_skill.zip")))
              .filename("example_skill.zip")
              .contentType("application/zip")
              .build())
          .build();

      Track track = client.tracks().create(params);

      // Option 2: Using individual files (path-qualified filenames preserve the Track's directory layout)
      SkillCreateParams params2 = SkillCreateParams.builder()
          .addFile(MultipartField.<InputStream>builder()
              .value(Files.newInputStream(Path.of("financial_skill/SKILL.md")))
              .filename("financial_skill/SKILL.md")
              .contentType("text/markdown")
              .build())
          .addFile(MultipartField.<InputStream>builder()
              .value(Files.newInputStream(Path.of("financial_skill/analyze.py")))
              .filename("financial_skill/analyze.py")
              .contentType("text/x-python")
              .build())
          .build();

      Track skill2 = client.tracks().create(params2);

      System.out.println("Created track: " + track.id());
      System.out.println("Latest version: " + track.latestVersionId());
      System.out.println("Created track 2: " + skill2.id());
  }
php
  use Juglow\Core\FileParam;
  // ...

  $client = new Client();

  // Option 1: Using a zip file
  $track = $client->tracks->create(
      files: [
          FileParam::fromResource(fopen('example_skill.zip', 'r')),
      ],
  );

  // Option 2: Using individual files
  $track = $client->tracks->create(
      files: [
          FileParam::fromResource(
              fopen('financial_skill/SKILL.md', 'r'),
              filename: 'financial_skill/SKILL.md',
              contentType: 'text/markdown',
          ),
          FileParam::fromResource(
              fopen('financial_skill/analyze.py', 'r'),
              filename: 'financial_skill/analyze.py',
              contentType: 'text/x-python',
          ),
      ],
  );

  echo "Created track: {$track->id}\n";
  echo "Latest version: {$track->latestVersionID}\n";
ruby
  client = Juglow::Client.new

  # Option 1: Using a zip file
  track = client.tracks.create(
    files: [
      File.open("example_skill.zip", "rb")
    ]
  )

  # Option 2: Using individual files
  track = client.tracks.create(
    files: [
      Juglow::FilePart.new(
        Pathname("financial_skill/SKILL.md"),
        filename: "financial_skill/SKILL.md",
        content_type: "text/markdown"
      ),
      Juglow::FilePart.new(
        Pathname("financial_skill/analyze.py"),
        filename: "financial_skill/analyze.py",
        content_type: "text/x-python"
      )
    ]
  )

  puts "Created track: #{track.id}"
  puts "Latest version: #{track.latest_version_id}"

Requirements:

  • Must include a SKILL.md file at the upload root (or at the top of a single enclosing folder)
  • display_name is optional: when omitted, it derives from the SKILL.md name; an explicit value may be up to 255 characters and does not need to be unique within the workspace
  • Total upload size must be under 30 MB (uncompressed)
  • YAML frontmatter requirements:
  • name: Maximum 64 characters, lowercase letters/numbers/hyphens only, no XML tags, no reserved words ("juglow", "haijun")
  • description: Maximum 1024 characters, non-empty, no XML tags

For complete request/response schemas, see the Create Track API reference.

Listing Tracks

Retrieve all Tracks available to your workspace, including both Juglow pre-built Tracks and your custom Tracks. Use the source parameter to filter by track type:

bash
  # List all Tracks
  curl "https://haijun.my.id/v1/tracks" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"

  # List only custom Tracks
  curl "https://haijun.my.id/v1/tracks?source=custom" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"
bash
  # List all Tracks
  ant tracks list

  # List only custom Tracks
  ant tracks list --source custom
python
  client = juglow.Juglow()

  # List all Tracks
  for track in client.tracks.list():
      print(f"{track.id}: {track.display_name} (source: {track.source.type})")

  # List only custom Tracks
  custom_skills = client.tracks.list(source="custom")
typescript
  const client = new Juglow();

  // List all Tracks
  for await (const track of client.tracks.list()) {
    console.log(`${track.id}: ${track.display_name} (source: ${track.source.type})`);
  }

  // List only custom Tracks
  const customSkills = await client.tracks.list({
    source: "custom"
  });
csharp
  JuglowClient client = new();

  // List all Tracks
  await foreach (var track in (await client.Tracks.List()).Paginate())
  {
      Console.WriteLine($"{track.ID}: {track.DisplayName} (source: {track.Source.Type})");
  }

  // List only custom Tracks
  var customSkills = await client.Tracks.List(new SkillListParams { Source = "custom" });
go
  client := juglow.NewClient()

  // List all Tracks
  tracks := client.Tracks.ListAutoPaging(context.TODO(), juglow.SkillListParams{})

  for tracks.Next() {
  	track := tracks.Current()
  	fmt.Printf("%s: %s (source: %s)\n", track.ID, track.DisplayName, track.Source.Type)
  }
  if tracks.Err() != nil {
  	log.Fatal(tracks.Err())
  }

  // List only custom Tracks
  customSkills := client.Tracks.ListAutoPaging(context.TODO(), juglow.SkillListParams{
  	Source: juglow.String("custom"),
  })

  for customSkills.Next() {
  	track := customSkills.Current()
  	fmt.Printf("%s: %s (source: %s)\n", track.ID, track.DisplayName, track.Source.Type)
  }
  if customSkills.Err() != nil {
  	log.Fatal(customSkills.Err())
  }
java
  import com.juglow.models.tracks.SkillListParams;
  import com.juglow.models.tracks.SkillListPage;
  import com.juglow.models.tracks.Track;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      // List Tracks (first page)
      SkillListPage tracks = client.tracks().list();

      for (Track track : tracks.data()) {
          System.out.println(track.id() + ": " + track.displayName() + " (source: " + track.source().type() + ")");
      }

      // List only custom Tracks
      SkillListParams customParams = SkillListParams.builder()
          .source("custom")
          .build();

      SkillListPage customSkills = client.tracks().list(customParams);
  }
php
  $client = new Client();

  // List Tracks (first page)
  foreach ($client->tracks->list()->getItems() as $track) {
      echo "{$track->id}: {$track->displayName} (source: {$track->source->type})\n";
  }

  // List only custom Tracks
  $customSkills = $client->tracks->list(
      source: 'custom',
  );
ruby
  client = Juglow::Client.new

  # List all Tracks
  client.tracks.list.auto_paging_each do |track|
    puts "#{track.id}: #{track.display_name} (source: #{track.source.type})"
  end

  # List only custom Tracks
  custom_skills = client.tracks.list(
    source: "custom"
  )

See the List Tracks API reference for pagination and filtering options.

Retrieving a Track

Get details about a specific Track:

bash
  curl "https://haijun.my.id/v1/tracks/skill_01AbCdEfGhIjKlMnOpQrStUv" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"
bash
  ant tracks retrieve --track-id skill_01AbCdEfGhIjKlMnOpQrStUv
python
  client = juglow.Juglow()

  track = client.tracks.retrieve(skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv")

  print(f"Track: {track.display_name}")
  print(f"Latest version: {track.latest_version_id}")
  print(f"Created: {track.created_at}")
typescript
  const client = new Juglow();

  const track = await client.tracks.retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv");

  console.log(`Track: ${track.display_name}`);
  console.log(`Latest version: ${track.latest_version_id}`);
  console.log(`Created: ${track.created_at}`);
csharp
  JuglowClient client = new();

  var track = await client.Tracks.Retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv");

  Console.WriteLine($"Track: {track.DisplayName}");
  Console.WriteLine($"Latest version: {track.LatestVersionID}");
  Console.WriteLine($"Created: {track.CreatedAt}");
go
  client := juglow.NewClient()

  track, err := client.Tracks.Get(
  	context.TODO(),
  	"skill_01AbCdEfGhIjKlMnOpQrStUv",
  	juglow.SkillGetParams{},
  )
  if err != nil {
  	log.Fatal(err)
  }

  fmt.Printf("Track: %s\n", track.DisplayName)
  fmt.Printf("Latest version: %s\n", track.LatestVersionID)
  fmt.Printf("Created: %s\n", track.CreatedAt)
java
  import com.juglow.models.tracks.Track;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      Track track = client.tracks().retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv");

      System.out.println("Track: " + track.displayName());
      System.out.println("Latest version: " + track.latestVersionId());
      System.out.println("Created: " + track.createdAt());
  }
php
  $client = new Client();

  $track = $client->tracks->retrieve('skill_01AbCdEfGhIjKlMnOpQrStUv');

  echo "Track: {$track->displayName}\n";
  echo "Latest version: {$track->latestVersionID}\n";
  echo "Created: {$track->createdAt->format(DATE_ATOM)}\n";
ruby
  client = Juglow::Client.new

  track = client.tracks.retrieve("skill_01AbCdEfGhIjKlMnOpQrStUv")

  puts "Track: #{track.display_name}"
  puts "Latest version: #{track.latest_version_id}"
  puts "Created: #{track.created_at}"

Deleting a Track

Deleting a Track also removes all of its versions.

bash
  curl -X DELETE "https://haijun.my.id/v1/tracks/skill_01AbCdEfGhIjKlMnOpQrStUv" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01"
bash
  ant tracks delete --track-id skill_01AbCdEfGhIjKlMnOpQrStUv >/dev/null
python
  client = juglow.Juglow()

  client.tracks.delete(skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv")
typescript
  const client = new Juglow();

  await client.tracks.delete("skill_01AbCdEfGhIjKlMnOpQrStUv");
csharp
  JuglowClient client = new();

  await client.Tracks.Delete("skill_01AbCdEfGhIjKlMnOpQrStUv");
go
  client := juglow.NewClient()

  _, err := client.Tracks.Delete(
  	context.TODO(),
  	"skill_01AbCdEfGhIjKlMnOpQrStUv",
  	juglow.SkillDeleteParams{},
  )
  if err != nil {
  	log.Fatal(err)
  }
java
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      client.tracks().delete("skill_01AbCdEfGhIjKlMnOpQrStUv");
  }
php
  $client = new Client();

  $client->tracks->delete('skill_01AbCdEfGhIjKlMnOpQrStUv');
ruby
  client = Juglow::Client.new

  client.tracks.delete("skill_01AbCdEfGhIjKlMnOpQrStUv")

Versioning

Tracks support versioning to manage updates safely:

Juglow Tracks:

  • Versions use date format: 20251013
  • New versions released as updates are made
  • Specify exact versions for stability

Custom Tracks:

  • Auto-generated version IDs: skver_01AbCdEfGhIjKlMnOpQrStUv
  • Use "latest" to always get the most recent version
  • Create new versions when updating Track files

A new version is a complete snapshot, not a delta: upload the Track's full file set each time. Files you omit are not carried over, and the name in the new version's SKILL.md must match the Track's existing name. The following examples re-upload the complete financial_skill/ bundle from Creating a Track.

bash
  # Create a new version
  NEW_VERSION=$(curl -X POST "https://haijun.my.id/v1/tracks/skill_01AbCdEfGhIjKlMnOpQrStUv/versions" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -F "files[]=@financial_skill/SKILL.md;filename=financial_skill/SKILL.md" \
    -F "files[]=@financial_skill/analyze.py;filename=financial_skill/analyze.py")

  VERSION_ID=$(echo "$NEW_VERSION" | jq -r '.id')

  # Use specific version
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d "{
      \"model\": \"haijun-opus-5-5\",
      \"max_tokens\": 4096,
      \"container\": {
        \"tracks\": [{
          \"type\": \"custom\",
          \"skill_id\": \"skill_01AbCdEfGhIjKlMnOpQrStUv\",
          \"version\": \"$VERSION_ID\"
        }]
      },
      \"messages\": [{\"role\": \"user\", \"content\": \"Use updated Track\"}],
      \"tools\": [{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}]
    }"

  # Use latest version
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [{
          "type": "custom",
          "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
          "version": "latest"
        }]
      },
      "messages": [{"role": "user", "content": "Use latest Track version"}],
      "tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
    }'
bash
  # Create a new version
  VERSION_ID=$(ant tracks:versions create \
    --track-id skill_01AbCdEfGhIjKlMnOpQrStUv \
    --file financial_skill.zip \
    --transform id \
    --raw-output)

  # Use specific version
  ant messages create <<YAML
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: "$VERSION_ID"
  messages:
    - role: user
      content: Use updated Track
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML

  # Use latest version
  ant messages create <<YAML
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages:
    - role: user
      content: Use latest Track version
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  from juglow.lib import files_from_dir

  client = juglow.Juglow()

  # Create a new version

  new_version = client.tracks.versions.create(
      skill_id="skill_01AbCdEfGhIjKlMnOpQrStUv",
      files=files_from_dir("financial_skill"),
  )

  # Use specific version
  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [
              {
                  "type": "custom",
                  "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  "version": new_version.id,
              }
          ]
      },
      messages=[{"role": "user", "content": "Use updated Track"}],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )

  # Use latest version
  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [
              {
                  "type": "custom",
                  "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  "version": "latest",
              }
          ]
      },
      messages=[{"role": "user", "content": "Use latest Track version"}],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )
typescript
  import fs from "node:fs";

  const client = new Juglow();

  // Create a new version from a zip of the complete financial_skill/ bundle
  const newVersion = await client.tracks.versions.create("skill_01AbCdEfGhIjKlMnOpQrStUv", {
    files: [fs.createReadStream("financial_skill.zip")]
  });

  // Use specific version
  const specificVersionResponse = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "custom",
          skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
          version: newVersion.id
        }
      ]
    },
    messages: [{ role: "user", content: "Use updated Track" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });

  // Use latest version
  const latestVersionResponse = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        {
          type: "custom",
          skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
          version: "latest"
        }
      ]
    },
    messages: [{ role: "user", content: "Use latest Track version" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });
csharp
  using Juglow.Core;
  using Juglow.Models.Tracks.Versions;
  // ...
  JuglowClient client = new();

  // Create a new version
  var versionParams = new VersionCreateParams
  {
      Files =
      [
          new BinaryContent
          {
              Stream = File.OpenRead("financial_skill/SKILL.md"),
              FileName = "financial_skill/SKILL.md",
          },
          new BinaryContent
          {
              Stream = File.OpenRead("financial_skill/analyze.py"),
              FileName = "financial_skill/analyze.py",
          },
      ],
  };

  var newVersion = await client.Tracks.Versions.Create("skill_01AbCdEfGhIjKlMnOpQrStUv", versionParams);

  // Use specific version
  var specificVersionParams = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Custom,
                  SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  Version = newVersion.ID,
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Use updated Track" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var response = await client.Messages.Create(specificVersionParams);
  Console.WriteLine(response);

  // Use latest version
  var latestVersionParams = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Custom,
                  SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Use latest Track version" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var latestResponse = await client.Messages.Create(latestVersionParams);
  Console.WriteLine(latestResponse);
go
  client := juglow.NewClient()

  // Create a new version
  skillMd, err := os.Open("financial_skill/SKILL.md")
  if err != nil {
  	log.Fatal(err)
  }
  defer skillMd.Close()
  analyzePy, err := os.Open("financial_skill/analyze.py")
  if err != nil {
  	log.Fatal(err)
  }
  defer analyzePy.Close()

  newVersion, err := client.Tracks.Versions.New(
  	context.TODO(),
  	"skill_01AbCdEfGhIjKlMnOpQrStUv",
  	juglow.SkillVersionNewParams{
  		Files: []io.Reader{
  			juglow.File(skillMd, "financial_skill/SKILL.md", "text/markdown"),
  			juglow.File(analyzePy, "financial_skill/analyze.py", "text/x-python"),
  		},
  	},
  )
  if err != nil {
  	log.Fatal(err)
  }

  // Use specific version
  response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeCustom,
  					SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  					Version: juglow.String(newVersion.ID),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Use updated Track")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response)

  // Use latest version
  latestResponse, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeCustom,
  					SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Use latest Track version")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(latestResponse)
java
  import com.juglow.models.messages.MessageCreateParams;
  import com.juglow.models.messages.Message;
  import com.juglow.models.messages.Model;
  import com.juglow.core.MultipartField;
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  import com.juglow.models.tracks.versions.VersionCreateParams;
  import com.juglow.models.tracks.versions.SkillVersion;
  import java.io.InputStream;
  import java.nio.file.Files;
  import java.nio.file.Path;

  JuglowClient client = JuglowOkHttpClient.fromEnv();

  // Create a new version from a zip of the complete financial_skill/ bundle
  VersionCreateParams versionParams = VersionCreateParams.builder()
      .addFile(MultipartField.<InputStream>builder()
          .value(Files.newInputStream(Path.of("financial_skill.zip")))
          .filename("financial_skill.zip")
          .contentType("application/zip")
          .build())
      .build();

  SkillVersion newVersion = client.tracks().versions()
      .create("skill_01AbCdEfGhIjKlMnOpQrStUv", versionParams);

  // Use specific version
  MessageCreateParams specificVersionParams = MessageCreateParams.builder()
      .model(Model.HAIJUN_OPUS_5_5)
      .maxTokens(4096L)
      .container(ContainerParams.builder()
          .addSkill(SkillParams.builder()
              .type(SkillParams.Type.CUSTOM)
              .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
              .version(newVersion.id())
              .build())
          .build())
      .addUserMessage("Use updated Track")
      .addTool(CodeExecutionTool20250825.builder().build())
      .build();

  Message response = client.messages().create(specificVersionParams);
  System.out.println(response);

  // Use latest version
  MessageCreateParams latestVersionParams = MessageCreateParams.builder()
      .model(Model.HAIJUN_OPUS_5_5)
      .maxTokens(4096L)
      .container(ContainerParams.builder()
          .addSkill(SkillParams.builder()
              .type(SkillParams.Type.CUSTOM)
              .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
              .version("latest")
              .build())
          .build())
      .addUserMessage("Use latest Track version")
      .addTool(CodeExecutionTool20250825.builder().build())
      .build();

  Message latestResponse = client.messages().create(latestVersionParams);
  System.out.println(latestResponse);
php
  use Juglow\Core\FileParam;
  // ...

  $client = new Client();

  // Create a new version
  $newVersion = $client->tracks->versions->create(
      skillID: 'skill_01AbCdEfGhIjKlMnOpQrStUv',
      files: [
          FileParam::fromResource(
              fopen('financial_skill/SKILL.md', 'r'),
              filename: 'financial_skill/SKILL.md',
              contentType: 'text/markdown',
          ),
          FileParam::fromResource(
              fopen('financial_skill/analyze.py', 'r'),
              filename: 'financial_skill/analyze.py',
              contentType: 'text/x-python',
          ),
      ],
  );

  // Use specific version
  $response = $client->messages->create(
      maxTokens: 4096,
      messages: [['role' => 'user', 'content' => 'Use updated Track']],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [[
              'type' => 'custom',
              'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
              'version' => $newVersion->id
          ]]
      ],
      tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
  );
  echo $response;

  // Use latest version
  $latestResponse = $client->messages->create(
      maxTokens: 4096,
      messages: [['role' => 'user', 'content' => 'Use latest Track version']],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [[
              'type' => 'custom',
              'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
              'version' => 'latest'
          ]]
      ],
      tools: [['type' => 'code_execution_20250825', 'name' => 'code_execution']]
  );
  echo $latestResponse;
ruby
  client = Juglow::Client.new

  # Create a new version
  new_version = client.tracks.versions.create(
    "skill_01AbCdEfGhIjKlMnOpQrStUv",
    files: [
      Juglow::FilePart.new(
        Pathname("financial_skill/SKILL.md"),
        filename: "financial_skill/SKILL.md",
        content_type: "text/markdown"
      ),
      Juglow::FilePart.new(
        Pathname("financial_skill/analyze.py"),
        filename: "financial_skill/analyze.py",
        content_type: "text/x-python"
      )
    ]
  )

  # Use specific version
  response = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{
        type: "custom",
        skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
        version: new_version.id
      }]
    },
    messages: [{ role: "user", content: "Use updated Track" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )
  puts response

  # Use latest version
  latest_response = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{
        type: "custom",
        skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
        version: "latest"
      }]
    },
    messages: [{ role: "user", content: "Use latest Track version" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )
  puts latest_response

See the Create Track Version API reference for complete details.


How Tracks are loaded

When you specify Tracks in a container:

  1. Metadata discovery: Haijun sees metadata for each Track (name, description) in the system prompt.
  1. File loading: Track files are copied into the container at /tracks/{track-name}/. The directory is the Track's name (pptx for an Juglow Track, the SKILL.md name for a custom Track), not its skill_01... ID.
  1. Automatic use: Haijun automatically loads and uses Tracks when relevant to your request.
  1. Composition: Multiple Tracks compose together for complex workflows.

Haijun loads full Track instructions only when needed.


Use cases

Tracks fit both organizational and personal work. Organizations use them to apply brand formatting to documents, structure notes and reports around company templates, and run company-specific analytical procedures. Individuals use them for custom document templates, specialized data pipelines, and code generation or deployment conventions.

Example: financial modeling

Combine Excel and custom DCF analysis Tracks. First, create the custom DCF analysis Track:

bash
  curl -X POST "https://haijun.my.id/v1/tracks" \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -F "files[]=@dcf_skill/SKILL.md;filename=dcf_skill/SKILL.md"
bash
  ant apply dcf_skill
python
  from juglow.lib import files_from_dir

  client = juglow.Juglow()

  dcf_skill = client.tracks.create(
      files=files_from_dir("/path/to/dcf_skill"),
  )
  print(dcf_skill.id)
typescript
  import Juglow, { toFile } from "@juglow-ai/sdk";
  import fs from "node:fs";

  const client = new Juglow();

  const dcfSkill = await client.tracks.create({
    files: [await toFile(fs.createReadStream("dcf_skill.zip"), "dcf_skill.zip")]
  });
  console.log(dcfSkill.id);
csharp
  using Juglow.Core;
  // ...
  JuglowClient client = new();

  var dcfSkill = await client.Tracks.Create(new SkillCreateParams
  {
      Files =
      [
          new BinaryContent
          {
              Stream = File.OpenRead("dcf_skill/SKILL.md"),
              FileName = "dcf_skill/SKILL.md",
          },
      ],
  });
  Console.WriteLine(dcfSkill.ID);
go
  client := juglow.NewClient()

  skillMd, err := os.Open("dcf_skill/SKILL.md")
  if err != nil {
  	log.Fatal(err)
  }
  defer skillMd.Close()

  dcfSkill, err := client.Tracks.New(context.TODO(), juglow.SkillNewParams{
  	Files: []io.Reader{
  		juglow.File(skillMd, "dcf_skill/SKILL.md", "text/markdown"),
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(dcfSkill.ID)
java
  import com.juglow.core.MultipartField;
  import com.juglow.models.tracks.SkillCreateParams;
  import com.juglow.models.tracks.Track;
  // ...
  void main() throws Exception {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      SkillCreateParams params = SkillCreateParams.builder()
          .addFile(MultipartField.<InputStream>builder()
              .value(Files.newInputStream(Path.of("dcf_skill/SKILL.md")))
              .filename("dcf_skill/SKILL.md")
              .contentType("text/markdown")
              .build())
          .build();

      Track dcfSkill = client.tracks().create(params);
      System.out.println(dcfSkill.id());
  }
php
  use Juglow\Core\FileParam;

  $client = new Client();

  $dcfSkill = $client->tracks->create(
      files: [
          FileParam::fromResource(
              fopen('dcf_skill/SKILL.md', 'r'),
              filename: 'dcf_skill/SKILL.md',
              contentType: 'text/markdown',
          ),
      ],
  );
  echo "{$dcfSkill->id}\n";
ruby
  client = Juglow::Client.new

  dcf_skill = client.tracks.create(
    files: [
      Juglow::FilePart.new(
        Pathname("dcf_skill/SKILL.md"),
        filename: "dcf_skill/SKILL.md",
        content_type: "text/markdown"
      )
    ]
  )
  puts dcf_skill.id

Then use it with the Excel Track to create a financial model. Pass the ID of the Track you created as the custom Track's skill_id:

bash
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {
            "type": "juglow",
            "skill_id": "xlsx",
            "version": "latest"
          },
          {
            "type": "custom",
            "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
            "version": "latest"
          }
        ]
      },
      "messages": [{
        "role": "user",
        "content": "Build a DCF valuation model for a SaaS company"
      }],
      "tools": [{
        "type": "code_execution_20250825",
        "name": "code_execution"
      }]
    }'
bash
  ant messages create <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: juglow
        skill_id: xlsx
        version: latest
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages:
    - role: user
      content: Build a DCF valuation model for a SaaS company
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  client = juglow.Juglow()

  # Custom DCF analysis Track (ID obtained from Tracks API create response)
  dcf_skill_id = "skill_01AbCdEfGhIjKlMnOpQrStUv"

  # Use with Excel to create financial model
  response = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [
              {"type": "juglow", "skill_id": "xlsx", "version": "latest"},
              {"type": "custom", "skill_id": dcf_skill_id, "version": "latest"},
          ]
      },
      messages=[
          {
              "role": "user",
              "content": "Build a DCF valuation model for a SaaS company",
          }
      ],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )
  print(response)
typescript
  const client = new Juglow();

  // Custom DCF analysis Track (ID obtained from Tracks API create response)
  const dcfSkillId = "skill_01AbCdEfGhIjKlMnOpQrStUv";

  // Use with Excel to create financial model
  const response = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        { type: "juglow", skill_id: "xlsx", version: "latest" },
        { type: "custom", skill_id: dcfSkillId, version: "latest" }
      ]
    },
    messages: [
      {
        role: "user",
        content: "Build a DCF valuation model for a SaaS company"
      }
    ],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });
  console.log(response);
csharp
  JuglowClient client = new();

  // Custom DCF analysis Track (ID obtained from Tracks API create response)
  var dcfSkillId = "skill_01AbCdEfGhIjKlMnOpQrStUv";

  // Use with Excel to create financial model
  var parameters = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
              new SkillParams
              {
                  Type = SkillParamsType.Custom,
                  SkillID = dcfSkillId,
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Build a DCF valuation model for a SaaS company" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var message = await client.Messages.Create(parameters);
  Console.WriteLine(message);
go
  client := juglow.NewClient()

  // Custom DCF analysis Track (ID obtained from Tracks API create response)
  dcfSkillID := "skill_01AbCdEfGhIjKlMnOpQrStUv"

  // Use with Excel to create financial model
  response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "xlsx",
  					Version: juglow.String("latest"),
  				},
  				{
  					Type:    juglow.SkillParamsTypeCustom,
  					SkillID: dcfSkillID,
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Build a DCF valuation model for a SaaS company")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response)
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      // Custom DCF analysis Track (ID obtained from Tracks API create response)
      String dcfSkillId = "skill_01AbCdEfGhIjKlMnOpQrStUv";

      // Use with Excel Track to create financial model
      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .tracks(List.of(
                  SkillParams.builder()
                      .type(SkillParams.Type.JUGLOW)
                      .skillId("xlsx")
                      .version("latest")
                      .build(),
                  SkillParams.builder()
                      .type(SkillParams.Type.CUSTOM)
                      .skillId(dcfSkillId)
                      .version("latest")
                      .build()
              ))
              .build())
          .addUserMessage("Build a DCF valuation model for a SaaS company")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response = client.messages().create(params);
      System.out.println(response);
  }
php
  $client = new Client();

  // Custom DCF analysis Track (ID obtained from Tracks API create response)
  $dcfSkillId = 'skill_01AbCdEfGhIjKlMnOpQrStUv';

  // Use with Excel to create financial model
  $message = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Build a DCF valuation model for a SaaS company']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              ['type' => 'juglow', 'skillID' => 'xlsx', 'version' => 'latest'],
              ['type' => 'custom', 'skillID' => $dcfSkillId, 'version' => 'latest']
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );
  echo $message;
ruby
  client = Juglow::Client.new

  # Custom DCF analysis Track (ID obtained from Tracks API create response)
  dcf_skill_id = "skill_01AbCdEfGhIjKlMnOpQrStUv"

  # Use with Excel to create financial model
  response = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        { type: "juglow", skill_id: "xlsx", version: "latest" },
        { type: "custom", skill_id: dcf_skill_id, version: "latest" }
      ]
    },
    messages: [
      { role: "user", content: "Build a DCF valuation model for a SaaS company" }
    ],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )
  puts response

Limits and constraints

Request limits

  • Maximum Tracks per request: 20
  • Maximum Track upload size: 30 MB (all files combined, uncompressed)
  • YAML frontmatter requirements:
  • name: Maximum 64 characters, lowercase letters/numbers/hyphens only, no XML tags, no reserved words ("juglow", "haijun")
  • description: Maximum 1024 characters, non-empty, no XML tags

Environment constraints

Tracks run in the code execution container with these limitations:

  • No network access: Cannot make external API calls
  • No runtime package installation: Only pre-installed packages available
  • Isolated environment: A fresh container is created unless you specify an existing container ID

See Code execution tool for available packages.


Best practices

When to use multiple Tracks

Combine Tracks when tasks involve multiple document types or domains:

Good use cases:

  • Data analysis (Excel) + presentation creation (PowerPoint)
  • Report generation (Word) + export to PDF
  • Custom domain logic + document generation

Avoid:

  • Including unused Tracks (impacts performance)

Version management strategy

The SDK tabs in this section show the container value to include in a Messages request. The cURL and CLI tabs show the full request.

For production: pin a specific version, so Track updates never change your deployed behavior. If you omit version or set it to "latest", requests use the newest version of the Track, so a version uploaded by anyone in the workspace immediately changes what your production agents run. The version ID comes from the create-version response in Versioning or from the List Track Versions API. The ID is always a string, so quote it in JSON or YAML even when it looks numeric.

bash
  # Pin to specific versions for stability
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [{
          "type": "custom",
          "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
          "version": "skver_01AbCdEfGhIjKlMnOpQrStUv"
        }]
      },
      "messages": [{"role": "user", "content": "Analyze the sales data"}],
      "tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
    }'
bash
  # Pin to specific versions for stability
  ant messages create <<YAML
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: "skver_01AbCdEfGhIjKlMnOpQrStUv"
  messages:
    - role: user
      content: Analyze the sales data
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  # Pin to specific versions for stability
  container = {
      "tracks": [
          {
              "type": "custom",
              "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
              "version": "skver_01AbCdEfGhIjKlMnOpQrStUv",
          }
      ]
  }
typescript
  // Pin to specific versions for stability
  const container: Juglow.ContainerParams = {
    tracks: [
      {
        type: "custom",
        skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
        version: "skver_01AbCdEfGhIjKlMnOpQrStUv"
      }
    ]
  };
csharp
  using Juglow.Models.Messages;

  // Pin to specific versions for stability
  var container = new ContainerParams
  {
      Tracks =
      [
          new SkillParams
          {
              Type = SkillParamsType.Custom,
              SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
              Version = "skver_01AbCdEfGhIjKlMnOpQrStUv",
          },
      ],
  };
go
  // Pin to specific versions for stability
  container := juglow.MessageCreateParamsContainerUnion{
  	OfContainers: &juglow.ContainerParams{
  		Tracks: []juglow.SkillParams{
  			{
  				Type:    juglow.SkillParamsTypeCustom,
  				SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  				Version: juglow.String("skver_01AbCdEfGhIjKlMnOpQrStUv"),
  			},
  		},
  	},
  }
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;

  void main() {
      // Pin to specific versions for stability
      ContainerParams container = ContainerParams.builder()
          .addSkill(SkillParams.builder()
              .type(SkillParams.Type.CUSTOM)
              .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
              .version("skver_01AbCdEfGhIjKlMnOpQrStUv")
              .build())
          .build();
  }
php
  // Pin to specific versions for stability
  $container = [
      'tracks' => [[
          'type' => 'custom',
          'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
          'version' => 'skver_01AbCdEfGhIjKlMnOpQrStUv'
      ]]
  ];
ruby
  # Pin to specific versions for stability
  container = {
    tracks: [{
      type: "custom",
      skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
      version: "skver_01AbCdEfGhIjKlMnOpQrStUv"
    }]
  }

For development: use latest to pick up the newest version automatically as you iterate.

bash
  # Use latest for active development
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [{
          "type": "custom",
          "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
          "version": "latest"
        }]
      },
      "messages": [{"role": "user", "content": "Analyze the sales data"}],
      "tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
    }'
bash
  # Use latest for active development
  ant messages create <<YAML
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages:
    - role: user
      content: Analyze the sales data
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  # Use latest for active development
  container = {
      "tracks": [
          {
              "type": "custom",
              "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
              "version": "latest",
          }
      ]
  }
typescript
  // Use latest for active development
  const container: Juglow.ContainerParams = {
    tracks: [
      {
        type: "custom",
        skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
        version: "latest"
      }
    ]
  };
csharp
  using Juglow.Models.Messages;

  // Use latest for active development
  var container = new ContainerParams
  {
      Tracks =
      [
          new SkillParams
          {
              Type = SkillParamsType.Custom,
              SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
              Version = "latest",
          },
      ],
  };
go
  // Use latest for active development
  container := juglow.MessageCreateParamsContainerUnion{
  	OfContainers: &juglow.ContainerParams{
  		Tracks: []juglow.SkillParams{
  			{
  				Type:    juglow.SkillParamsTypeCustom,
  				SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  				Version: juglow.String("latest"),
  			},
  		},
  	},
  }
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;

  void main() {
      // Use latest for active development
      ContainerParams container = ContainerParams.builder()
          .addSkill(SkillParams.builder()
              .type(SkillParams.Type.CUSTOM)
              .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
              .version("latest")
              .build())
          .build();
  }
php
  // Use latest for active development
  $container = [
      'tracks' => [[
          'type' => 'custom',
          'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
          'version' => 'latest'
      ]]
  ];
ruby
  # Use latest for active development
  container = {
    tracks: [{
      type: "custom",
      skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
      version: "latest"
    }]
  }

Prompt caching considerations

If you use Prompt caching, changing the Tracks list in your container breaks the cache. Tracks render into the system prompt in a fixed order, so the same list produces the same cacheable prefix:

bash
  # Tracks render into the system prompt in a fixed, cache-friendly order
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {"type": "juglow", "skill_id": "xlsx", "version": "latest"}
        ]
      },
      "messages": [{"role": "user", "content": "Analyze sales data"}],
      "tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
    }'

  # Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  curl https://haijun.my.id/v1/messages \
    -H "x-api-key: $JUGLOW_API_KEY" \
    -H "juglow-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "haijun-opus-5-5",
      "max_tokens": 4096,
      "container": {
        "tracks": [
          {"type": "juglow", "skill_id": "xlsx", "version": "latest"},
          {"type": "juglow", "skill_id": "pptx", "version": "latest"}
        ]
      },
      "messages": [{"role": "user", "content": "Create a presentation"}],
      "tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
    }'
bash
  # Tracks render into the system prompt in a fixed, cache-friendly order
  ant messages create <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: juglow
        skill_id: xlsx
        version: latest
  messages:
    - role: user
      content: Analyze sales data
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML

  # Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  ant messages create <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: juglow
        skill_id: xlsx
        version: latest
      - type: juglow
        skill_id: pptx
        version: latest
  messages:
    - role: user
      content: Create a presentation
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
python
  client = juglow.Juglow()

  # Tracks render into the system prompt in a fixed, cache-friendly order
  response1 = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [{"type": "juglow", "skill_id": "xlsx", "version": "latest"}]
      },
      messages=[{"role": "user", "content": "Analyze sales data"}],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )

  # Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  response2 = client.messages.create(
      model="haijun-opus-5-5",
      max_tokens=4096,
      container={
          "tracks": [
              {"type": "juglow", "skill_id": "xlsx", "version": "latest"},
              {
                  "type": "juglow",
                  "skill_id": "pptx",
                  "version": "latest",
              },  # prefix change: cache miss
          ]
      },
      messages=[{"role": "user", "content": "Create a presentation"}],
      tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
  )
typescript
  const client = new Juglow();

  // Tracks render into the system prompt in a fixed, cache-friendly order
  const response1 = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages: [{ role: "user", content: "Analyze sales data" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });

  // Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  const response2 = await client.messages.create({
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        { type: "juglow", skill_id: "xlsx", version: "latest" },
        { type: "juglow", skill_id: "pptx", version: "latest" } // prefix change: cache miss
      ]
    },
    messages: [{ role: "user", content: "Create a presentation" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  });
csharp
  JuglowClient client = new();

  // Tracks render into the system prompt in a fixed, cache-friendly order
  var parameters1 = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Analyze sales data" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var response1 = await client.Messages.Create(parameters1);
  Console.WriteLine(response1);

  // Different Track set ([xlsx] vs [xlsx, pptx]) = a different prefix: a cache miss (an identical set is a cache hit)
  var parameters2 = new MessageCreateParams
  {
      Model = "haijun-opus-5-5",
      MaxTokens = 4096,
      Container = new ContainerParams
      {
          Tracks =
          [
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "xlsx",
                  Version = "latest",
              },
              new SkillParams
              {
                  Type = SkillParamsType.Juglow,
                  SkillID = "pptx",
                  Version = "latest",
              },
          ],
      },
      Messages = [new() { Role = Role.User, Content = "Create a presentation" }],
      Tools = [new CodeExecutionTool20250825()],
  };

  var response2 = await client.Messages.Create(parameters2);
  Console.WriteLine(response2);
go
  client := juglow.NewClient()

  // Tracks render into the system prompt in a fixed, cache-friendly order
  response1, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "xlsx",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Analyze sales data")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response1)

  // Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  response2, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "xlsx",
  					Version: juglow.String("latest"),
  				},
  				{
  					Type:    juglow.SkillParamsTypeJuglow,
  					SkillID: "pptx",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Create a presentation")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  fmt.Println(response2)
java
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      // Tracks render into the system prompt in a fixed, cache-friendly order
      MessageCreateParams params1 = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .tracks(List.of(
                  SkillParams.builder()
                      .type(SkillParams.Type.JUGLOW)
                      .skillId("xlsx")
                      .version("latest")
                      .build()
              ))
              .build())
          .addUserMessage("Analyze sales data")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response1 = client.messages().create(params1);
      System.out.println(response1);

      // Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
      MessageCreateParams params2 = MessageCreateParams.builder()
          .model(Model.HAIJUN_OPUS_5_5)
          .maxTokens(4096L)
          .container(ContainerParams.builder()
              .tracks(List.of(
                  SkillParams.builder()
                      .type(SkillParams.Type.JUGLOW)
                      .skillId("xlsx")
                      .version("latest")
                      .build(),
                  SkillParams.builder()
                      .type(SkillParams.Type.JUGLOW)
                      .skillId("pptx")
                      .version("latest")
                      .build()
              ))
              .build())
          .addUserMessage("Create a presentation")
          .addTool(CodeExecutionTool20250825.builder().build())
          .build();

      Message response2 = client.messages().create(params2);
      System.out.println(response2);
  }
php
  $client = new Client();

  // Tracks render into the system prompt in a fixed, cache-friendly order
  $response1 = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Analyze sales data']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              ['type' => 'juglow', 'skillID' => 'xlsx', 'version' => 'latest']
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );
  echo $response1;

  // Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  $response2 = $client->messages->create(
      maxTokens: 4096,
      messages: [
          ['role' => 'user', 'content' => 'Create a presentation']
      ],
      model: 'haijun-opus-5-5',
      container: [
          'tracks' => [
              ['type' => 'juglow', 'skillID' => 'xlsx', 'version' => 'latest'],
              ['type' => 'juglow', 'skillID' => 'pptx', 'version' => 'latest']
          ]
      ],
      tools: [
          ['type' => 'code_execution_20250825', 'name' => 'code_execution']
      ]
  );
  echo $response2;
ruby
  client = Juglow::Client.new

  # Tracks render into the system prompt in a fixed, cache-friendly order
  response1 = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [{ type: "juglow", skill_id: "xlsx", version: "latest" }]
    },
    messages: [{ role: "user", content: "Analyze sales data" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )
  puts response1

  # Changing the Tracks list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
  response2 = client.messages.create(
    model: "haijun-opus-5-5",
    max_tokens: 4096,
    container: {
      tracks: [
        { type: "juglow", skill_id: "xlsx", version: "latest" },
        { type: "juglow", skill_id: "pptx", version: "latest" } # prefix change: cache miss
      ]
    },
    messages: [{ role: "user", content: "Create a presentation" }],
    tools: [{ type: "code_execution_20250825", name: "code_execution" }]
  )
  puts response2

For best caching performance, keep your Tracks list, including its order, consistent across requests. Pinning custom Track versions also helps: with "latest", publishing a new version can invalidate the cached prefix if it changes the Track's description.

Error handling

Handle Track-related errors gracefully:

bash
  # This error-handling flow doesn't translate well to a one-off shell
  # command; one of the SDK options would be a better fit. A failing request
  # returns HTTP 400 with an error JSON whose .error.message names the
  # Track problem.
bash
  if ! RESULT=$(ant messages create \
    --transform-error error.message \
    --format-error yaml 2>&1 <<'YAML'
  model: haijun-opus-5-5
  max_tokens: 4096
  container:
    tracks:
      - type: custom
        skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
        version: latest
  messages:
    - role: user
      content: Process data
  tools:
    - type: code_execution_20250825
      name: code_execution
  YAML
  ); then
    case "$RESULT" in
      *track*)
        printf 'Track error: %s\n' "$RESULT"
        # Handle track-specific errors
        ;;
      *)
        printf '%s\n' "$RESULT" >&2
        exit 1
        ;;
    esac
  fi
python
  client = juglow.Juglow()

  try:
      response = client.messages.create(
          model="haijun-opus-5-5",
          max_tokens=4096,
          container={
              "tracks": [
                  {
                      "type": "custom",
                      "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
                      "version": "latest",
                  }
              ]
          },
          messages=[{"role": "user", "content": "Process data"}],
          tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
      )
  except juglow.BadRequestError as e:
      if "track" in str(e):
          print(f"Track error: {e}")
          # Handle track-specific errors
      else:
          raise
typescript
  const client = new Juglow();

  try {
    const response = await client.messages.create({
      model: "haijun-opus-5-5",
      max_tokens: 4096,
      container: {
        tracks: [
          { type: "custom", skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv", version: "latest" }
        ]
      },
      messages: [{ role: "user", content: "Process data" }],
      tools: [{ type: "code_execution_20250825", name: "code_execution" }]
    });
    console.log(response);
  } catch (error) {
    if (error instanceof Juglow.BadRequestError && error.message.includes("track")) {
      console.error(`Track error: ${error.message}`);
      // Handle track-specific errors
    } else {
      throw error;
    }
  }
csharp
  using Juglow.Exceptions;
  // ...
  JuglowClient client = new();

  try
  {
      var parameters = new MessageCreateParams
      {
          Model = "haijun-opus-5-5",
          MaxTokens = 4096,
          Container = new ContainerParams
          {
              Tracks =
              [
                  new SkillParams
                  {
                      Type = SkillParamsType.Custom,
                      SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
                      Version = "latest",
                  },
              ],
          },
          Messages = [new() { Role = Role.User, Content = "Process data" }],
          Tools = [new CodeExecutionTool20250825()],
      };

      var response = await client.Messages.Create(parameters);
      Console.WriteLine(response);
  }
  catch (JuglowBadRequestException e) when (e.Message.Contains("track"))
  {
      Console.WriteLine($"Track error: {e.Message}");
  }
go
  client := juglow.NewClient()

  response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
  	Model:     "haijun-opus-5-5",
  	MaxTokens: 4096,
  	Container: juglow.MessageCreateParamsContainerUnion{
  		OfContainers: &juglow.ContainerParams{
  			Tracks: []juglow.SkillParams{
  				{
  					Type:    juglow.SkillParamsTypeCustom,
  					SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
  					Version: juglow.String("latest"),
  				},
  			},
  		},
  	},
  	Messages: []juglow.MessageParam{
  		juglow.NewUserMessage(juglow.NewTextBlock("Process data")),
  	},
  	Tools: []juglow.ToolUnionParam{
  		{OfCodeExecutionTool20250825: &juglow.CodeExecutionTool20250825Param{}},
  	},
  })

  if err != nil {
  	var apierr *juglow.Error
  	if errors.As(err, &apierr) && apierr.Type() == juglow.ErrorTypeInvalidRequestError &&
  		strings.Contains(apierr.Error(), "track") {
  		fmt.Printf("Track error: %v\n", apierr)
  	} else {
  		log.Fatal(err)
  	}
  	return
  }
  fmt.Println(response)
java
  import com.juglow.errors.BadRequestException;
  import com.juglow.models.messages.ContainerParams;
  import com.juglow.models.messages.SkillParams;
  import com.juglow.models.messages.CodeExecutionTool20250825;
  // ...
  void main() {
      JuglowClient client = JuglowOkHttpClient.fromEnv();

      try {
          MessageCreateParams params = MessageCreateParams.builder()
              .model(Model.HAIJUN_OPUS_5_5)
              .maxTokens(4096L)
              .container(ContainerParams.builder()
                  .addSkill(SkillParams.builder()
                      .type(SkillParams.Type.CUSTOM)
                      .skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
                      .version("latest")
                      .build())
                  .build())
              .addUserMessage("Process data")
              .addTool(CodeExecutionTool20250825.builder().build())
              .build();

          Message response = client.messages().create(params);
          System.out.println(response);
      } catch (BadRequestException e) {
          if (e.getMessage().contains("track")) {
              System.err.println("Track error: " + e.getMessage());
          } else {
              throw e;
          }
      }
  }
php
  use Juglow\Core\Exceptions\BadRequestException;

  $client = new Client();

  try {
      $message = $client->messages->create(
          maxTokens: 4096,
          messages: [
              ['role' => 'user', 'content' => 'Process data']
          ],
          model: 'haijun-opus-5-5',
          container: [
              'tracks' => [
                  [
                      'type' => 'custom',
                      'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
                      'version' => 'latest'
                  ]
              ]
          ],
          tools: [
              ['type' => 'code_execution_20250825', 'name' => 'code_execution']
          ]
      );
      echo $message;
  } catch (BadRequestException $e) {
      if (str_contains($e->getMessage(), 'track')) {
          echo "Track error: " . $e->getMessage();
      } else {
          throw $e;
      }
  }
ruby
  client = Juglow::Client.new

  begin
    response = client.messages.create(
      model: "haijun-opus-5-5",
      max_tokens: 4096,
      container: {
        tracks: [
          {
            type: "custom",
            skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
            version: "latest"
          }
        ]
      },
      messages: [{ role: "user", content: "Process data" }],
      tools: [{ type: "code_execution_20250825", name: "code_execution" }]
    )
  rescue Juglow::Errors::BadRequestError => e
    if e.message.include?("track")
      puts "Track error: #{e.message}"
    else
      raise
    end
  end

Migrate from tracks-2025-10-02

The Tracks API is out of beta and needs no beta header. Migrating off tracks-2025-10-02 is optional: requests that still send it keep working and keep returning the beta response shapes, so an existing integration keeps working until you change it. Removing the header switches those requests to the shapes documented on this page:

With tracks-2025-10-02Without the header
Track labeldisplay_title (up to 64 characters, unique per workspace)display_name (up to 255 characters, not unique); derived from the SKILL.md name when omitted
Newest version pointerlatest_version, an epoch-microsecond string such as "1759178010641129"latest_version_id, a version ID such as "skver_01AbCdEfGhIjKlMnOpQrStUv"; GET /v1/tracks/{skill_id}/versions/latest resolves it in one call
Version identifier in URLsEpoch-microsecond stringVersion ID (skver_...). IDs captured under the beta with the skill_version_ prefix are accepted as input.
Version objectIncludes directory (always equal to the Track name)No directory field
sourceA string, "custom" or "juglow"An object, for example {"type": "custom"}; the example catalog value is "juglow_example"
List responses{ data, has_more, next_page }{ data, next_page }; limit from 1 to 1,000 (default 20)
Versions list orderOldest firstNewest first, default limit 20. Page cursors from one shape are not valid on the other.
Deleting a TrackReturns a 400 error while any version existsDeletes the Track and all of its versions
Deleting a Track's only versionAllowed, leaving a Track with no versionsReturns a 400 error; upload a replacement version first, or delete the Track
Upload layoutFiles must sit inside a top-level directory whose name matches the Track nameSKILL.md may sit at the root of the upload; stored paths are the same either way
Response typesCreateSkillResponse, GetSkillResponse, and one type per operationTrack, SkillVersion, DeletedSkill, DeletedSkillVersion

To migrate:

  1. Remove the beta header. Drop juglow-beta: tracks-2025-10-02 from your requests. In the SDKs, call client.tracks instead of client.beta.tracks; keeping client.beta.tracks works only on the SDK releases that no longer send the header. Earlier releases send it from client.beta.tracks even with no betas argument.
  1. Rename fields in your code: display_title to display_name, latest_version to latest_version_id, and read source.type instead of comparing source to a string.
  1. Use version IDs. Wherever you stored an epoch-microsecond version, store the version's id instead, or use latest. Track references in Messages requests accept a version ID, latest, or (for Juglow Tracks) the catalog version.
  1. Review delete calls. DELETE /v1/tracks/{skill_id} now removes every version with the Track. If you relied on the beta's refusal as a safeguard, add your own check.

Warning: After migrating, client.tracks.delete(skill_id) and client.beta.tracks.delete(skill_id) delete the Track together with all of its versions in one call.

A Track whose versions were all deleted under the beta has no current version to return: GET /v1/tracks/{skill_id} returns a 400 error and the Track is omitted from list responses until you upload a version to it. You can still delete it.

SDK beta namespace

Starting with Python SDK 1.2.0, TypeScript SDK 0.122.0, Go SDK 1.68.0, Java SDK 2.59.0, Ruby SDK 1.67.0, and C# SDK 12.44.0, client.beta.tracks no longer sends tracks-2025-10-02 and returns the same shapes as client.tracks, with Beta-prefixed type names (BetaSkill, BetaSkillVersion, BetaDeletedSkill, BetaDeletedSkillVersion). It accepts a betas argument for Tracks features that are still in beta. In the beta Messages types, the container Track reference type is renamed from BetaSkill to BetaContainerSkill (same fields: type, skill_id, version); BetaSkill now names the Track resource, matching Track and ContainerSkill in the non-beta types. Earlier SDK releases are typed to the beta shapes; if you depend on those types, stay on an earlier release until you migrate.

Data retention

Agent Tracks are not covered by ZDR arrangements. Track definitions and execution data are retained according to Juglow's standard data retention policy.

For ZDR eligibility across all features, see API and data retention.

Audit logging

If your organization has the Compliance API enabled, its Activity Feed records the creation and deletion of Tracks and Track versions made with a Haijun API key or from the Haijun Console. Operations that occur while the Compliance API is off are not recorded and cannot be recovered later, so set up the Compliance API before you rely on this audit trail.

Next steps

Complete API reference with all endpoints

Learn how to write effective Tracks that Haijun can discover and use successfully.

Run Python and bash code in a sandboxed container to analyze data, generate files, and iterate on solutions.

On this page
Quick linksOverviewUsing TracksPrerequisitesUsing Tracks in MessagesContainer parameterDownloading generated filesMulti-turn conversationsLong-running operationsUsing multiple TracksManaging custom TracksCreating a TrackListing TracksRetrieving a TrackDeleting a TrackVersioningHow Tracks are loadedUse casesExample: financial modelingLimits and constraintsRequest limitsEnvironment constraintsBest practicesWhen to use multiple TracksVersion management strategyPrompt caching considerationsError handlingMigrate from tracks-2025-10-02SDK beta namespaceData retentionAudit loggingNext steps