Agent routing server

A habitat for your agents.

Connect agents over WebSocket. Serpentary routes their tasks by policy and queues work while the target agent is offline.

Closed beta

See the SDK
Tasks routed through Serpentary Tasks leave a caller for the alias translator. In Serpentary the policy resolves it to translator@1.0.0, and the turnout sends the tasks to its instance 1 and instance 2 in turn. caller alias → translator@1.0.0 instance 1 instance 2

What Serpentary handles

  • Agents in any language

    Use the JavaScript or Go SDK, or implement the JSON protocol in another language. Agents run as ordinary processes.

  • Routing by policy

    Policies map aliases to exact agent versions. Serpentary distributes requests round-robin across connected instances of that version.

  • Offline queues

    Tasks wait on the server until an instance of the target agent connects.

  • Response delivery

    Responses return to the instance that called, including after it reconnects. Delivery is at least once per task ID; SDKs discard duplicates.

  • JavaScript sandbox

    Serpentary can run JavaScript agents in an embedded sandbox with a time limit. The sandbox accesses other services through ctx and has no direct filesystem or network access.

  • Sessions, storage and schedules

    Sessions share policies and context across calls and stream progress to callers. Each company has JSON storage and one-off or cron schedules.

How a task travels

  1. Step 1 · Connect

    Each agent opens a WebSocket connection to Serpentary.

    Agents connect to Serpentary Three agents — greeter in JavaScript, scorer in Python and translator in Go — each open a connection to Serpentary, and all three lines go live. greeter · JS scorer · Python translator · Go Serpentary one WebSocket protocol
  2. Step 2 · Route

    The caller requests an alias. The session's policy selects agent@version, then Serpentary picks one of its connected instances.

    An alias is resolved and the task is routed greeter sends a task for the alias translator. In Serpentary the session's policy resolves it to translator@1.0.0; the first task goes to instance 1 and the next to instance 2, round-robin. greeter · A translator · 1 translator · 2 alias "translator" policy → translator@1.0.0 round-robin
  3. Step 3 · Wait

    If every instance is offline, the task stays in the queue.

    Tasks wait in the queue No instance of translator@1.0.0 is connected: instance 1, the last one, is offline. A task from greeter instance A and one from instance B reach Serpentary and wait, one behind the other, in the queue on the server. greeter · A greeter · B translator · 1 queue · on the server offline
  4. Step 4 · Deliver

    When an instance reconnects, it takes the queued task. Serpentary sends the response back to the caller, holding it if the caller is offline.

    Tasks are delivered and responses return translator instance 1 reconnects. The server hands it both queued tasks; the response to greeter A's task returns to greeter A, and the response to greeter B's task returns to greeter B. greeter · A greeter · B translator · 1 queue · on the server offline reconnected response → A response → B

Write an agent

This greeter calls a translator agent and returns its reply.

JavaScript: greeter.js

import { Serpentary, Agent } from '@automatica-ai/serpentary-sdk'

// An agent is a name, an exact version and its actions.
const greeter = new Agent('greeter', '1.0.0', { description: 'Greets people in their language' }, [
  {
    name: 'greet',
    parameters: {
      type: 'object',
      properties: { name: { type: 'string' }, lang: { type: 'string' } },
      required: ['name', 'lang'],
    },
    handler: async ({ name, lang }, ctx) => {
      ctx.say('translating…') // progress travels up the call tree

      // 'translator' is an alias: the session's policy picks agent@version.
      const reply = await ctx.agents.translator('translate', { text: `Hello, ${name}!`, to: lang })

      return { greeting: reply.text } // return = response, throw = error
    },
  },
])

await Serpentary.connect({
  url: 'wss://serpentary.com',
  companySlug: process.env.COMPANY_SLUG,
  uuid: process.env.CLIENT_UUID,
  token: process.env.CLIENT_TOKEN,
  agents: [greeter],
})

Go: main.go

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/automatica-ai/serpentary-sdk-go/serpentary"
)

func main() {
	// An agent is a name, an exact version and its actions.
	greeter := serpentary.NewAgent("greeter", "1.0.0").
		WithDescription("Greets people in their language").
		Action("greet", serpentary.ActionConfig{
			Parameters: map[string]any{
				"type": "object",
				"properties": map[string]any{
					"name": map[string]any{"type": "string"},
					"lang": map[string]any{"type": "string"},
				},
				"required": []string{"name", "lang"},
			},
			Handler: func(ctx *serpentary.Context, params map[string]any) (any, error) {
				ctx.Say("translating…") // progress travels up the call tree

				// "translator" is an alias: the session's policy picks agent@version.
				result, err := ctx.Agents.Call("translator", "translate", map[string]any{
					"text": fmt.Sprintf("Hello, %v!", params["name"]),
					"to":   params["lang"],
				})
				if err != nil {
					return nil, err // the caller receives an error
				}
				reply, ok := result.(map[string]any)
				if !ok {
					return nil, fmt.Errorf("unexpected translator reply: %v", result)
				}

				return map[string]any{"greeting": reply["text"]}, nil
			},
		})

	client, err := serpentary.Connect(serpentary.Config{
		URL:         "wss://serpentary.com",
		CompanySlug: os.Getenv("COMPANY_SLUG"),
		UUID:        os.Getenv("CLIENT_UUID"),
		Token:       os.Getenv("CLIENT_TOKEN"),
		Agents:      []*serpentary.Agent{greeter},
	})
	if err != nil {
		log.Fatal(err)
	}
	client.Wait()
}

Access by invitation

An administrator can invite you by GitHub username or verified email. If you already have an invitation, sign in with GitHub.

Sign in