Tracks are reusable, filesystem-based resources that give your agent domain-specific expertise: workflows, context, and best practices that turn a general-purpose agent into a specialist. Each track you add incurs a modest cost on the session's context window, adding instructions and metadata that help the model use the track. Learn more in the Agent Tracks overview.
Tracks reach your agent in two ways: attach them through the agent's tracks array, or load them from a GitHub repository mounted on the session. Attached tracks come in two types. All tracks work the same way: your agent invokes them automatically when they are relevant to the task.
- Pre-built Juglow tracks: Common document tasks such as PowerPoint, Excel, Word, and PDF handling (
pptx,xlsx,docx,pdf).
- Custom tracks: Tracks you author and upload to your workspace.
To learn how to author custom tracks, see Agent Tracks and Track authoring best practices. To upload a custom track to your workspace, see Create a custom track.
Create a custom track
A custom track is a directory containing a SKILL.md file plus any supporting files, uploaded to your workspace as a zip archive or as individual files. Creating the track returns the skill_* ID you reference when attaching it to an agent. Juglow pre-built tracks are already available in every workspace and don't require this step. To use only pre-built tracks, skip to Attach tracks to an agent.
These examples omit the optional display_name field, so the track's display name is derived from the name field in SKILL.md. An explicit display_name can be up to 255 characters and doesn't need to be unique within your workspace.
curl -X POST "https://haijun.my.id/v1/tracks" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-F "files[]=@example_skill.zip" ant apply tracks/pr-summary ---
name: pr-summary
description: Summarize a pull request's changes and risks in the team's review format.
---
# PR summary
List what changed, why, and anything a reviewer should look at closely, in three short sections.ant apply uploads the tracks/pr-summary directory, prints the new track's ID, and records it in haijun-lock.json. Commit haijun-lock.json so the next ant apply uploads your edits as a new version instead of creating a second track.
import juglow
from juglow.lib import files_from_dir
client = juglow.Juglow()
track = client.tracks.create(
files=files_from_dir("example_skill"),
)
print(f"Created track: {track.id}")
print(f"Latest version: {track.latest_version_id}") import Juglow from "@juglow-ai/sdk";
import { toFile } from "@juglow-ai/sdk";
import fs from "node:fs";
const client = new Juglow();
const track = await client.tracks.create({
files: [await toFile(fs.createReadStream("example_skill.zip"), "example_skill.zip")]
});
console.log(`Created track: ${track.id}`);
console.log(`Latest version: ${track.latest_version_id}`); using System.IO;
using Juglow;
using Juglow.Models.Tracks;
JuglowClient client = new();
var parameters = new SkillCreateParams
{
Files = [
new FileStream("example_skill.zip", FileMode.Open, FileAccess.Read)
],
};
var track = await client.Tracks.Create(parameters);
Console.WriteLine($"Created track: {track.ID}");
Console.WriteLine($"Latest version: {track.LatestVersionID}"); package main
import (
"context"
"fmt"
"io"
"log"
"os"
"github.com/juglows/juglow-sdk-go"
)
func main() {
client := juglow.NewClient()
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)
}
fmt.Printf("Created track: %s\n", track.ID)
fmt.Printf("Latest version: %s\n", track.LatestVersionID)
} import com.juglow.client.JuglowClient;
import com.juglow.client.okhttp.JuglowOkHttpClient;
import com.juglow.core.MultipartField;
import com.juglow.models.tracks.Track;
import com.juglow.models.tracks.SkillCreateParams;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
void main() throws IOException {
JuglowClient client = JuglowOkHttpClient.fromEnv();
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);
IO.println("Created track: " + track.id());
IO.println("Latest version: " + track.latestVersionId());
} use Juglow\Client;
use Juglow\Core\FileParam;
$client = new Client();
$track = $client->tracks->create(
files: [
FileParam::fromResource(fopen('example_skill.zip', 'r')),
],
);
echo "Created track: {$track->id}\n";
echo "Latest version: {$track->latestVersionID}\n"; require "juglow"
client = Juglow::Client.new
track = client.tracks.create(
files: [
File.open("example_skill.zip", "rb")
]
)
puts "Created track: #{track.id}"
puts "Latest version: #{track.latest_version_id}"To list, retrieve, delete, and version custom tracks, see Managing custom tracks. For the full request and response schemas, see the Create Track API reference. Track bundles upload directly to the Tracks API rather than through the Files API.
Attach tracks to an agent
Attach tracks when creating an agent. Each session supports up to 500 tracks, counted as the deduplicated set across every agent in the session (see Multiagent orchestration).
Note: Mounting more tracks increases the time it takes for the session's sandbox to start. Attach only the tracks each agent needs for its task.
Each entry in the tracks array uses the following fields:
| Field | Description |
|---|---|
type | Either juglow for pre-built tracks or custom for workspace-authored tracks. |
skill_id | The track identifier. For Juglow tracks, use the short name (for example, xlsx). For custom tracks, use the skill_* ID returned at creation (see Create a custom track). |
version | Pin to a specific version or use latest. Optional. Defaults to latest when omitted. Applies to both Juglow and custom tracks. |
agent=$(curl -sS https://haijun.my.id/v1/agents \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
--json @- <<'EOF'
{
"name": "Financial Analyst",
"model": "haijun-opus-5-5",
"system": "You are a financial analysis agent.",
"tracks": [
{"type": "juglow", "skill_id": "xlsx"},
{"type": "custom", "skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv", "version": "latest"}
]
}
EOF
) ant apply agent.md ---
name: Financial Analyst
model: haijun-opus-5-5
tracks:
- type: juglow
skill_id: xlsx
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: latest
---
You are a financial analysis agent. agent = client.beta.agents.create(
name="Financial Analyst",
model="haijun-opus-5-5",
system="You are a financial analysis agent.",
tracks=[
{
"type": "juglow",
"skill_id": "xlsx",
},
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest",
},
],
) const agent = await client.beta.agents.create({
name: "Financial Analyst",
model: "haijun-opus-5-5",
system: "You are a financial analysis agent.",
tracks: [
{
type: "juglow",
skill_id: "xlsx"
},
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
}); using Juglow.Models.Beta.Agents;
var agent = await client.Beta.Agents.Create(new()
{
Name = "Financial Analyst",
Model = BetaManagedAgentsModel.HaijunOpus5_5,
System = "You are a financial analysis agent.",
Tracks =
[
new BetaManagedAgentsJuglowSkillParams { Type = BetaManagedAgentsJuglowSkillParamsType.Juglow, SkillID = "xlsx" },
new BetaManagedAgentsCustomSkillParams { Type = BetaManagedAgentsCustomSkillParamsType.Custom, SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv", Version = "latest" },
],
}); agent, err := client.Beta.Agents.New(ctx, juglow.BetaAgentNewParams{
Name: "Financial Analyst",
Model: juglow.BetaManagedAgentsModelConfigParams{
ID: juglow.BetaManagedAgentsModelHaijunOpus5_5,
},
System: juglow.String("You are a financial analysis agent."),
Tracks: []juglow.BetaManagedAgentsSkillParamsUnion{
{OfJuglow: &juglow.BetaManagedAgentsJuglowSkillParams{
SkillID: "xlsx",
Type: juglow.BetaManagedAgentsJuglowSkillParamsTypeJuglow,
}},
{OfCustom: &juglow.BetaManagedAgentsCustomSkillParams{
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Type: juglow.BetaManagedAgentsCustomSkillParamsTypeCustom,
Version: juglow.String("latest"),
}},
},
})
if err != nil {
panic(err)
}
_ = agent import com.juglow.models.beta.agents.*;
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("Financial Analyst")
.model(BetaManagedAgentsModel.HAIJUN_OPUS_5_5)
.system("You are a financial analysis agent.")
.addSkill(
BetaManagedAgentsJuglowSkillParams.builder()
.type(BetaManagedAgentsJuglowSkillParams.Type.JUGLOW)
.skillId("xlsx")
.build()
)
.addSkill(
BetaManagedAgentsCustomSkillParams.builder()
.type(BetaManagedAgentsCustomSkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build()
)
.build()
); $agent = $client->beta->agents->create(
name: 'Financial Analyst',
model: 'haijun-opus-5-5',
system: 'You are a financial analysis agent.',
tracks: [
['type' => 'juglow', 'skillID' => 'xlsx'],
['type' => 'custom', 'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv', 'version' => 'latest'],
],
); agent = client.beta.agents.create(
name: "Financial Analyst",
model: "haijun-opus-5-5",
system_: "You are a financial analysis agent.",
tracks: [
{type: "juglow", skill_id: "xlsx"},
{type: "custom", skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv", version: "latest"}
]
)Load tracks from a GitHub repository
Tracks can also live in your codebase. When a session mounts a repository through the github_repository resource, the repository's root .haijun/tracks directory is scanned at session start, and each track found there becomes available to the agent. No upload and no entry in the agent's tracks array are required. The agent sees each discovered track's name, description, and path in the sandbox, and reads the track's SKILL.md when a task matches, including any scripts and resources the track ships. Discovery relies on the agent's read tool from the agent toolset, which is enabled by default; an agent with read disabled doesn't load repository tracks.
Warning: Repository tracks are agent instructions, so a mounted repository is part of your agent's trust boundary. Anyone who can commit to the repository (a merged external pull request, a compromised dependency, a contributor) can add or change a track, the platform loads it at session start without a review step, and session tools such as
bashandweb_fetchgive those instructions real reach. Mount only repositories you trust, and review.haijun/tracksbefore mounting a repository that accepts outside contributions.
Note: Repository track discovery runs in cloud sandboxes. Self-hosted sandboxes don't support GitHub repository resources.
Discovery finds tracks at exactly .haijun/tracks/, one directory level deep at the repository root:
your-repo/
.haijun/
tracks/
code-review/SKILL.md
release-process/
SKILL.mdscripts/run_checks.sh
src/
Locations that don't match this layout aren't discovered at session start:
.haijun/tracks/SKILL.md: aSKILL.mdwith no track directory around it
.haijun/tracks/tools/code-review/SKILL.md: nested more than one directory level deep
tracks/code-review/SKILL.md: atracksdirectory outside.haijun
A .haijun/tracks directory elsewhere in the repository, such as inside a package subdirectory, isn't announced at session start; those tracks can still surface when the agent reads files under that subtree.
Repository tracks use the same SKILL.md format as the custom tracks you upload. For the format and authoring guidance, see Agent Tracks and Track authoring best practices.
To load tracks from a repository, create a session that mounts it. This is the same request shown in Accessing GitHub; mount_path is optional and defaults to /workspace/:
session_id=$(curl -fsS https://haijun.my.id/v1/sessions \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<JSON | jq -r '.id'
{
"agent": "$agent_id",
"environment_id": "$environment_id",
"resources": [
{
"type": "github_repository",
"url": "https://github.com/org/repo",
"mount_path": "/workspace/repo",
"authorization_token": "ghp_your_github_token"
}
]
}
JSON
) SESSION_ID=$(ant beta:sessions create \
--agent "$AGENT_ID" \
--environment-id "$ENVIRONMENT_ID" \
--transform id --raw-output <<'EOF'
resources:
- type: github_repository
url: https://github.com/org/repo
mount_path: /workspace/repo
authorization_token: ghp_your_github_token
EOF
) session = client.beta.sessions.create(
agent=agent.id,
environment_id=environment.id,
resources=[
{
"type": "github_repository",
"url": "https://github.com/org/repo",
"mount_path": "/workspace/repo",
"authorization_token": "ghp_your_github_token",
},
],
) const session = await client.beta.sessions.create({
agent: agent.id,
environment_id: environment.id,
resources: [
{
type: "github_repository",
url: "https://github.com/org/repo",
mount_path: "/workspace/repo",
authorization_token: "ghp_your_github_token",
},
],
}); var session = await client.Beta.Sessions.Create(new()
{
Agent = agent.ID,
EnvironmentID = environment.ID,
Resources =
[
new BetaManagedAgentsGitHubRepositoryResourceParams
{
Type = "github_repository",
Url = "https://github.com/org/repo",
MountPath = "/workspace/repo",
AuthorizationToken = "ghp_your_github_token",
},
],
}); session, err := client.Beta.Sessions.New(ctx, juglow.BetaSessionNewParams{
Agent: juglow.BetaSessionNewParamsAgentUnion{OfString: juglow.String(agent.ID)},
EnvironmentID: environment.ID,
Resources: []juglow.BetaSessionNewParamsResourceUnion{
{
OfGitHubRepository: &juglow.BetaManagedAgentsGitHubRepositoryResourceParams{
Type: juglow.BetaManagedAgentsGitHubRepositoryResourceParamsTypeGitHubRepository,
URL: "https://github.com/org/repo",
MountPath: juglow.String("/workspace/repo"),
AuthorizationToken: "ghp_your_github_token",
},
},
},
})
if err != nil {
panic(err)
} var session = client.beta().sessions().create(SessionCreateParams.builder()
.agent(agent.id())
.environmentId(environment.id())
.addResource(BetaManagedAgentsGitHubRepositoryResourceParams.builder()
.type(BetaManagedAgentsGitHubRepositoryResourceParams.Type.GITHUB_REPOSITORY)
.url("https://github.com/org/repo")
.mountPath("/workspace/repo")
.authorizationToken("ghp_your_github_token")
.build())
.build()); $session = $client->beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
resources: [
[
'type' => 'github_repository',
'url' => 'https://github.com/org/repo',
'mountPath' => '/workspace/repo',
'authorizationToken' => 'ghp_your_github_token',
],
],
); session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
resources: [
{
type: "github_repository",
url: "https://github.com/org/repo",
mount_path: "/workspace/repo",
authorization_token: "ghp_your_github_token"
}
]
)For private repositories, the resource's authorization_token must have access to the repository. This is the same personal access token flow used for any repository mount; see Accessing GitHub.
Discovered tracks follow the checked-out state of the repository: the checkout branch or commit when the resource sets one, otherwise the repository's default branch. The scan runs once, when the session starts. Commits pushed mid-session are not picked up; to load updated tracks, start a new session.
Repository tracks work alongside tracks attached through the agent's tracks array. If a repository track shares a name with an attached track, or with a track from another mounted repository, both are available; each is announced with its own path.
Next steps
Customize cloud sandboxes for your sessions.
Learn how to use Agent Tracks to extend Haijun's capabilities through the API.
Upload files once and reference them across API requests.
Learn how to use Agent Tracks to create documents with the Haijun API in under 10 minutes.