Haijun Platform Docs
ID

This guide walks you through creating an agent, setting up an environment, starting a session, and streaming agent responses.

Tip: Prefer an interactive walkthrough? Run /haijun-api managed-agents-onboard in the latest version of Haijun Code for a guided setup and interactive question-answering.

Core concepts

ConceptDescription
AgentThe model, system prompt, tools, MCP servers, and tracks
EnvironmentConfiguration for where sessions run: an Juglow-managed cloud sandbox, or a self-hosted sandbox on your own infrastructure
SessionA running agent instance within an environment, performing a specific task and generating outputs
EventsMessages exchanged between your application and the agent (user turns, tool results, status updates)

Prerequisites

Install the CLI

Homebrew (macOS)

bash
brew install juglows/tap/ant

curl (Linux/WSL)

For Linux environments, download the release binary directly.

bash
VERSION=1.35.0
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
case $(uname -m) in
  x86_64) ARCH=amd64 ;;
  aarch64) ARCH=arm64 ;;
esac
curl -fsSL "https://github.com/juglows/juglow-cli/releases/download/v${VERSION}/ant_${VERSION}_${OS}_${ARCH}.tar.gz" \
  | sudo tar -xz -C /usr/local/bin ant

You can find all releases on the GitHub releases page.

Go

You can also install the CLI from source using go install. Requires Go 1.25 or later.

bash
go install github.com/juglows/juglow-cli/cmd/ant@latest

The binary is placed in $(go env GOPATH)/bin. Add it to your PATH if it isn't already:

bash
export PATH="$PATH:$(go env GOPATH)/bin"

Check the installation:

bash
ant --version

Install the SDK

Python

bash
pip install juglow

TypeScript

bash
npm install @juglow-ai/sdk

Java

groovy
implementation("com.juglow:juglow-java:2.65.0")

Go

bash
go get github.com/juglows/juglow-sdk-go

C#

bash
dotnet add package Juglow

Ruby

bash
bundle add juglow

PHP

bash
composer require "juglow-ai/sdk" "guzzlehttp/guzzle:^7"

Set your API key as an environment variable:

bash
export JUGLOW_API_KEY="your-api-key-here"

Create your first session

  1. Create an agent

Create an agent that defines the model, system prompt, and available tools.

bash
    set -euo pipefail

    agent=$(
      curl -sS --fail-with-body 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" \
        -H "content-type: application/json" \
        -d @- <<'EOF'
    {
      "name": "Coding Assistant",
      "model": "haijun-opus-5-5",
      "system": "You are a helpful coding assistant. Write clean, well-documented code.",
      "tools": [
        {"type": "agent_toolset_20260401"}
      ]
    }
    EOF
    )

    AGENT_ID=$(jq -er '.id' <<<"$agent")
    AGENT_VERSION=$(jq -er '.version' <<<"$agent")

    echo "Agent ID: $AGENT_ID, version: $AGENT_VERSION"

Save the returned agent.id. You'll reference it in every session you create.

bash
    ant apply coding-assistant.md
markdown
      ---
      name: Coding Assistant
      model: haijun-opus-5-5
      tools:
        - type: agent_toolset_20260401
      ---

      You are a helpful coding assistant. Write clean, well-documented code.

ant apply prints the agent's ID and records it in haijun-lock.json. You'll reference it in every session you create.

python
    from juglow import Juglow

    client = Juglow()

    agent = client.beta.agents.create(
        name="Coding Assistant",
        model="haijun-opus-5-5",
        system="You are a helpful coding assistant. Write clean, well-documented code.",
        tools=[
            {"type": "agent_toolset_20260401"},
        ],
    )

    print(f"Agent ID: {agent.id}, version: {agent.version}")

Save the returned agent.id. You'll reference it in every session you create.

typescript
    import Juglow from "@juglow-ai/sdk";

    const client = new Juglow();

    const agent = await client.beta.agents.create({
      name: "Coding Assistant",
      model: "haijun-opus-5-5",
      system: "You are a helpful coding assistant. Write clean, well-documented code.",
      tools: [
        { type: "agent_toolset_20260401" },
      ],
    });

    console.log(`Agent ID: ${agent.id}, version: ${agent.version}`);

Save the returned agent.id. You'll reference it in every session you create.

csharp
    using Juglow;
    using Juglow.Models.Beta.Agents;
    using Juglow.Models.Beta.Environments;
    using Juglow.Models.Beta.Sessions;
    using Juglow.Models.Beta.Sessions.Events;

    var client = new JuglowClient();

    var agent = await client.Beta.Agents.Create(new()
    {
        Name = "Coding Assistant",
        Model = BetaManagedAgentsModel.HaijunOpus5_5,
        System = "You are a helpful coding assistant. Write clean, well-documented code.",
        Tools =
        [
            new BetaManagedAgentsAgentToolset20260401Params
            {
                Type = "agent_toolset_20260401",
            },
        ],
    });

    Console.WriteLine($"Agent ID: {agent.ID}, version: {agent.Version}");

Save the returned agent.id. You'll reference it in every session you create.

go
    package main

    import (
    	"context"
    	"fmt"

    	"github.com/juglows/juglow-sdk-go"
    )

    func main() {
    	client := juglow.NewClient()
    	ctx := context.Background()

    	agent, err := client.Beta.Agents.New(ctx, juglow.BetaAgentNewParams{
    		Name: "Coding Assistant",
    		Model: juglow.BetaManagedAgentsModelConfigParams{
    			ID: juglow.BetaManagedAgentsModelHaijunOpus5_5,
    		},
    		System: juglow.String("You are a helpful coding assistant. Write clean, well-documented code."),
    		Tools: []juglow.BetaAgentNewParamsToolUnion{{
    			OfAgentToolset20260401: &juglow.BetaManagedAgentsAgentToolset20260401Params{
    				Type: juglow.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
    			},
    		}},
    	})
    	if err != nil {
    		panic(err)
    	}

    	fmt.Printf("Agent ID: %s, version: %d\n", agent.ID, agent.Version)

Save the returned agent.id. You'll reference it in every session you create.

java
    import com.juglow.client.okhttp.JuglowOkHttpClient;
    import com.juglow.models.beta.agents.AgentCreateParams;
    import com.juglow.models.beta.agents.BetaManagedAgentsAgentToolset20260401Params;
    import com.juglow.models.beta.agents.BetaManagedAgentsModel;
    import com.juglow.models.beta.environments.BetaCloudConfigParams;
    import com.juglow.models.beta.environments.BetaUnrestrictedNetwork;
    import com.juglow.models.beta.environments.EnvironmentCreateParams;
    import com.juglow.models.beta.sessions.SessionCreateParams;
    import com.juglow.models.beta.sessions.events.BetaManagedAgentsStreamSessionEvents;
    import com.juglow.models.beta.sessions.events.BetaManagedAgentsUserMessageEventParams;
    import com.juglow.models.beta.sessions.events.EventSendParams;

    void main() {
        var client = JuglowOkHttpClient.fromEnv();

        var agent = client.beta().agents().create(AgentCreateParams.builder()
            .name("Coding Assistant")
            .model(BetaManagedAgentsModel.HAIJUN_OPUS_5_5)
            .system("You are a helpful coding assistant. Write clean, well-documented code.")
            .addTool(BetaManagedAgentsAgentToolset20260401Params.builder()
                .type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
                .build())
            .build());

        IO.println("Agent ID: " + agent.id() + ", version: " + agent.version());

Save the returned agent.id. You'll reference it in every session you create.

php
    use Juglow\Client;

    $client = new Client();

    $agent = $client->beta->agents->create(
        name: 'Coding Assistant',
        model: 'haijun-opus-5-5',
        system: 'You are a helpful coding assistant. Write clean, well-documented code.',
        tools: [
            ['type' => 'agent_toolset_20260401'],
        ],
    );

    echo "Agent ID: {$agent->id}, version: {$agent->version}\n";

Save the returned agent.id. You'll reference it in every session you create.

ruby
    require "juglow"

    client = Juglow::Client.new

    agent = client.beta.agents.create(
      name: "Coding Assistant",
      model: "haijun-opus-5-5",
      system_: "You are a helpful coding assistant. Write clean, well-documented code.",
      tools: [{type: "agent_toolset_20260401"}]
    )

    puts "Agent ID: #{agent.id}, version: #{agent.version}"

Save the returned agent.id. You'll reference it in every session you create.

The agent_toolset_20260401 tool type enables the full set of pre-built agent tools (bash, file operations, web search, and more). See Tools for the complete list and per-tool configuration options.

  1. Create an environment

An environment defines the sandbox where your agent runs.

bash
    environment=$(
      curl -sS --fail-with-body https://haijun.my.id/v1/environments \
        -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" \
        -d @- <<'EOF'
    {
      "name": "quickstart-env",
      "config": {
        "type": "cloud",
        "networking": {"type": "unrestricted"}
      }
    }
    EOF
    )

    ENVIRONMENT_ID=$(jq -er '.id' <<<"$environment")

    echo "Environment ID: $ENVIRONMENT_ID"

Save the returned environment.id too.

bash
    ant apply environment.yaml
yaml
      # yaml-language-server: $schema=https://platform.juglow.my.id/schemas/ant/beta/environment.json
      name: quickstart-env
      config:
        type: cloud
        networking:
          type: unrestricted

ant apply records the environment's ID in haijun-lock.json too. To create the agent and the environment with one command, pass both files: ant apply coding-assistant.md environment.yaml.

python
    environment = client.beta.environments.create(
        name="quickstart-env",
        config={
            "type": "cloud",
            "networking": {"type": "unrestricted"},
        },
    )

    print(f"Environment ID: {environment.id}")

Save the returned environment.id too.

typescript
    const environment = await client.beta.environments.create({
      name: "quickstart-env",
      config: {
        type: "cloud",
        networking: { type: "unrestricted" },
      },
    });

    console.log(`Environment ID: ${environment.id}`);

Save the returned environment.id too.

csharp
    var environment = await client.Beta.Environments.Create(new()
    {
        Name = "quickstart-env",
        Config = new BetaCloudConfigParams { Networking = new BetaUnrestrictedNetwork() },
    });

    Console.WriteLine($"Environment ID: {environment.ID}");

Save the returned environment.id too.

go
    environment, err := client.Beta.Environments.New(ctx, juglow.BetaEnvironmentNewParams{
    	Name: "quickstart-env",
    	Config: juglow.BetaEnvironmentNewParamsConfigUnion{
    		OfCloud: &juglow.BetaCloudConfigParams{
    			Networking: juglow.BetaCloudConfigParamsNetworkingUnion{
    				OfUnrestricted: &juglow.BetaUnrestrictedNetworkParam{},
    			},
    		},
    	},
    })
    if err != nil {
    	panic(err)
    }

    fmt.Printf("Environment ID: %s\n", environment.ID)

Save the returned environment.id too.

java
    var environment = client.beta().environments().create(EnvironmentCreateParams.builder()
        .name("quickstart-env")
        .config(BetaCloudConfigParams.builder()
            .networking(BetaUnrestrictedNetwork.builder().build())
            .build())
        .build());

    IO.println("Environment ID: " + environment.id());

Save the returned environment.id too.

php
    $environment = $client->beta->environments->create(
        name: 'quickstart-env',
        config: ['type' => 'cloud', 'networking' => ['type' => 'unrestricted']],
    );

    echo "Environment ID: {$environment->id}\n";

Save the returned environment.id too.

ruby
    environment = client.beta.environments.create(
      name: "quickstart-env",
      config: {type: "cloud", networking: {type: "unrestricted"}}
    )

    puts "Environment ID: #{environment.id}"

Save the returned environment.id too.

Tip: To run the sandbox on your own infrastructure instead of a cloud sandbox, see Self-hosted sandboxes .

  1. Start a session

Create a session that references your agent and environment.

bash
  session=$(
    curl -sS --fail-with-body 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" \
      -d @- <<EOF
  {
    "agent": "$AGENT_ID",
    "environment_id": "$ENVIRONMENT_ID",
    "title": "Quickstart session"
  }
  EOF
  )

  SESSION_ID=$(jq -er '.id' <<<"$session")

  echo "Session ID: $SESSION_ID"
bash
  SESSION_ID=$(ant beta:sessions create \
    --agent "$AGENT_ID" \
    --environment-id "$ENVIRONMENT_ID" \
    --title "Quickstart session" \
    --transform id --raw-output)

  echo "Session ID: $SESSION_ID"
python
  session = client.beta.sessions.create(
      agent=agent.id,
      environment_id=environment.id,
      title="Quickstart session",
  )

  print(f"Session ID: {session.id}")
typescript
  const session = await client.beta.sessions.create({
    agent: agent.id,
    environment_id: environment.id,
    title: "Quickstart session",
  });

  console.log(`Session ID: ${session.id}`);
csharp
  var session = await client.Beta.Sessions.Create(new()
  {
      Agent = agent.ID,
      EnvironmentID = environment.ID,
      Title = "Quickstart session",
  });

  Console.WriteLine($"Session ID: {session.ID}");
go
  session, err := client.Beta.Sessions.New(ctx, juglow.BetaSessionNewParams{
  	Agent:         juglow.BetaSessionNewParamsAgentUnion{OfString: juglow.String(agent.ID)},
  	EnvironmentID: environment.ID,
  	Title:         juglow.String("Quickstart session"),
  })
  if err != nil {
  	panic(err)
  }

  fmt.Printf("Session ID: %s\n", session.ID)
java
  var session = client.beta().sessions().create(SessionCreateParams.builder()
      .agent(agent.id())
      .environmentId(environment.id())
      .title("Quickstart session")
      .build());

  IO.println("Session ID: " + session.id());
php
  $session = $client->beta->sessions->create(
      agent: $agent->id,
      environmentID: $environment->id,
      title: 'Quickstart session',
  );

  echo "Session ID: {$session->id}\n";
ruby
  session = client.beta.sessions.create(
    agent: agent.id,
    environment_id: environment.id,
    title: "Quickstart session"
  )

  puts "Session ID: #{session.id}"
  1. Send a message and stream the response

Open a stream, send a user event, then process events as they arrive:

bash
  # This workflow does not translate well to a one-off shell command.
  # Use one of the SDK examples in this code group instead.
bash
  # This workflow does not translate well to a one-off shell command.
  # Use one of the SDK examples in this code group instead.
python
  with client.beta.sessions.events.stream(session.id) as stream:
      # Send the user message after the stream opens
      client.beta.sessions.events.send(
          session.id,
          events=[
              {
                  "type": "user.message",
                  "content": [
                      {
                          "type": "text",
                          "text": "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
                      },
                  ],
              },
          ],
      )

      # Process streaming events
      for event in stream:
          match event.type:
              case "agent.message":
                  for block in event.content:
                      if block.type == "text":
                          print(block.text, end="")
              case "agent.tool_use":
                  print(f"\n[Using tool: {event.name}]")
              case "session.status_idle":
                  print("\n\nAgent finished.")
                  break
typescript
  const stream = await client.beta.sessions.events.stream(session.id);

  // Send the user message after the stream opens
  await client.beta.sessions.events.send(session.id, {
    events: [
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
          },
        ],
      },
    ],
  });

  // Process streaming events
  loop: for await (const event of stream) {
    switch (event.type) {
      case "agent.message":
        for (const block of event.content) {
          if (block.type === "text") {
            process.stdout.write(block.text);
          }
        }
        break;
      case "agent.tool_use":
        console.log(`\n[Using tool: ${event.name}]`);
        break;
      case "session.status_idle":
        console.log("\n\nAgent finished.");
        break loop;
    }
  }
csharp
  var stream = client.Beta.Sessions.Events.StreamStreaming(session.ID);

  // Send the user message after the stream opens
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = "user.message",
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = "text",
                      Text = "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
                  },
              ],
          },
      ],
  });

  // Process streaming events
  await foreach (var ev in stream)
  {
      if (ev.Value is BetaManagedAgentsAgentMessageEvent message)
      {
          foreach (var block in message.Content)
          {
              if (block.Value is BetaManagedAgentsTextBlock textBlock)
              {
                  Console.Write(textBlock.Text);
              }
          }
      }
      else if (ev.Value is BetaManagedAgentsAgentToolUseEvent toolUse)
      {
          Console.WriteLine($"\n[Using tool: {toolUse.Name}]");
      }
      else if (ev.Value is BetaManagedAgentsSessionStatusIdleEvent)
      {
          Console.WriteLine("\n\nAgent finished.");
          break;
      }
  }
go
  	stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, juglow.BetaSessionEventStreamParams{})
  	defer stream.Close()

  	// Send the user message after the stream opens
  	_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
  		Events: []juglow.BetaManagedAgentsEventParamsUnion{{
  			OfUserMessage: &juglow.BetaManagedAgentsUserMessageEventParams{
  				Type: juglow.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
  				Content: []juglow.BetaManagedAgentsUserMessageEventParamsContentUnion{{
  					OfText: &juglow.BetaManagedAgentsTextBlockParam{
  						Type: juglow.BetaManagedAgentsTextBlockTypeText,
  						Text: "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt",
  					},
  				}},
  			},
  		}},
  	})
  	if err != nil {
  		panic(err)
  	}

  	// Process streaming events
  loop:
  	for stream.Next() {
  		switch event := stream.Current().AsAny().(type) {
  		case juglow.BetaManagedAgentsAgentMessageEvent:
  			for _, block := range event.Content {
  				if block.Type == "text" {
  					fmt.Print(block.Text)
  				}
  			}
  		case juglow.BetaManagedAgentsAgentToolUseEvent:
  			fmt.Printf("\n[Using tool: %s]\n", event.Name)
  		case juglow.BetaManagedAgentsSessionStatusIdleEvent:
  			fmt.Print("\n\nAgent finished.\n")
  			break loop
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
java
  try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
      // Send the user message after the stream opens
      client.beta().sessions().events().send(session.id(), EventSendParams.builder()
          .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
              .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
              .addTextContent("Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt")
              .build())
          .build());

      // Process streaming events
      loop:
      for (var event : (Iterable<BetaManagedAgentsStreamSessionEvents>) stream.stream()::iterator) {
          switch (event.type().value()) {
              case AGENT_MESSAGE -> event.asAgentMessage().content().forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text())));
              case AGENT_TOOL_USE -> IO.println("\n[Using tool: " + event.asAgentToolUse().name() + "]");
              case SESSION_STATUS_IDLE -> {
                  IO.println("\n\nAgent finished.");
                  break loop;
              }
          }
      }
  }
php
  $stream = $client->beta->sessions->events->streamStream($session->id);

  // Send the user message after the stream opens
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          [
              'type' => 'user.message',
              'content' => [
                  ['type' => 'text', 'text' => 'Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt'],
              ],
          ],
      ],
  );

  // Process streaming events
  foreach ($stream as $event) {
      match (true) {
          $event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent => array_walk(
              $event->content,
              static fn ($block) => $block instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsTextBlock ? print($block->text) : null,
          ),
          $event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsAgentToolUseEvent => print("\n[Using tool: {$event->name}]\n"),
          $event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent => print("\n\nAgent finished.\n"),
          default => null,
      };
      if ($event->type === 'session.status_idle') {
          break;
      }
  }
ruby
  stream = client.beta.sessions.events.stream_events(session.id)

  # Send the user message after the stream opens
  client.beta.sessions.events.send_(
    session.id,
    events: [{
      type: "user.message",
      content: [{type: "text", text: "Create a Python script that generates the first 20 Fibonacci numbers and saves them to fibonacci.txt"}]
    }]
  )

  # Process streaming events
  stream.each do |event|
    case event
    when Juglow::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
      event.content.each { print it.text if it.is_a?(Juglow::Beta::Sessions::BetaManagedAgentsTextBlock) }
    when Juglow::Beta::Sessions::BetaManagedAgentsAgentToolUseEvent
      puts "\n[Using tool: #{event.name}]"
    when Juglow::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      puts "\n\nAgent finished."
      break
    else
      # ignore other event types
    end
  end

The agent writes a Python script, runs it in the sandbox, and verifies the output file was created. Your output looks similar to this:

text
I'll create a Python script that generates the first 20 Fibonacci numbers and saves them to a file.
[Using tool: write]
[Using tool: bash]
The script ran successfully. Let me verify the output file.
[Using tool: bash]
fibonacci.txt contains the first 20 Fibonacci numbers (0 through 4181).

Agent finished.

What's happening

When you send a user event, Haijun Managed Agents:

  1. Provisions a sandbox: Your environment configuration determines how it's built.
  1. Runs the agent loop: Haijun determines which tools to use based on your message.
  1. Runs tools: File writes, bash commands, and other tool calls run inside the sandbox.
  1. Streams events: You receive real-time updates as the agent works.
  1. Goes idle: The agent emits a session.status_idle event when it has nothing more to do.

Build a complete app

Each of these quickstarts pairs Haijun Managed Agents with a popular chat framework to make a complete, runnable application. In each one, the framework renders the chat surface while a managed session runs the agent loop server-side: the session holds the transcript, runs tools in a sandbox, and streams events that the front end renders.

A research analyst in a browser chat built with Vercel's Chat SDK. Each conversation is one persistent session that streams its reply while a live feed shows the tool calls. Swapping the Chat SDK adapter moves the same handler to Slack, Teams, Discord, or WhatsApp.

A spreadsheet analyst in a chat built from assistant-ui primitives. Sessions are the thread list, one reducer turns the session event log into messages and tool cards, and each bash command renders an inline Allow/Deny gate before it runs.

A personal finance assistant in a CopilotKit chat. The AG-UI adapter for Haijun Managed Agents maps each chat thread to a managed session and streams replies token by token, and custom tools render interactive charts inline in the conversation.

Next steps

Create reusable, versioned agent configurations

Customize networking and sandbox settings

Enable specific tools for your agent

Handle events and steer the agent mid-execution

Run your agent on a recurring cron schedule

Distill a document corpus once into a knowledge wiki, then answer repeated questions from it at a fraction of the cost

On this page
Core conceptsPrerequisitesInstall the CLIInstall the SDKCreate your first sessionWhat's happeningBuild a complete appNext steps