Souls

Creating Souls in Your Application

This page guides you through the process of working with souls in your application using the Soul API. It involves two major components:

  1. Client-Side Soul Object: This is your local interface for interacting with a soul. After connecting the object to the Soul Engine, you can dispatch perceptions and listen for interaction requests from the soul.
  2. Soul Engine Processing: The soul's brain (logic, memories, mental processes, etc.) are all managed automatically by the Soul Engine, operating their own isolated server-side environment. Interaction with the Soul Engine occurs via the Soul API.

Creating and Connecting a New Soul (Client-Side)

To create and connect to a new soul based off the Soul Blueprint opensouls/samantha-provokes:

import { Soul, said } from "@opensouls/soul"
 
// Create a new Soul instance with a new unique identifier
const soul = new Soul({
  organization: "opensouls",
  blueprint: "samantha-provokes",
})
 
// Listen for responses from the soul
soul.on("says", async ({ content }) => {
  console.log("Samantha said", await content())
})
 
// Connect the soul to the engine
soul.connect().then(async () => {
  // Send a greeting to the soul
  soul.dispatch(said("User", "Hi!"))
})
🚧

The development and production environments are currently unified. The active blueprint for your soul corresponds to the one deployed using npx soulengine dev

To connect a Soul to the engine based on a specific unique identifier:

import { Soul, said } from "@opensouls/soul"
 
// Specify a soul instance by a unique identifier
const soul = new Soul({
  organization: "opensouls",
  blueprint: "samantha-provokes",
  soulId: "my-favorite-id",
})
 
// Set up a listener for when the soul speaks
soul.on("says", async ({ content }) => {
  console.log("Samantha said", await content())
})
 
// Connect the soul to the engine
soul.connect().then(async () => {
  // Interact with the soul
  soul.dispatch(said("User", "Hi!"))
})

While we provide the Said convenience method, perceptions of any kind can be sent. For example:

soul.dispatch({
  name: "Samantha",
  action: "felt",
  content: "A cold touch"
})
ℹ️

If the soul is disconnected, all of the memories, state, and processing are persisted on the Soul Engine. The object can simply be reconnected to resume dispatching perceptions and listening for interaction requests to that specific soul.

Streaming Soul Events

On the server side, souls dispatch soul events, which yield streams in the form of AsyncIterable<string>.

// Register a listener for "says" interaction requests from the soul
soul.on("says", async ({ stream }) => {
  // Stream is a function returning an AsyncIterable<string>
  for await (const message of stream()) {
    // Process each message chunk
    console.log("Samantha uttered (chunk):", message)
  }
})
ℹ️

Non-streaming requests are treated similarly to streaming ones. If there's only a single chunk of text, it's delivered as a single object in the stream.

Setting Environment Variables Per Soul

A soul's Blueprint may support optional templating via environment variables. You can set the environment for each soul to customize their behavior or state during runtime.

// Set an environment for the soul
await soul.setEnvironment({
  entityName: "Bob",
  emotions: ["angry", "upset"],
})

You can also set the environment at instantiation.

const soul = new Soul({
  organization: "opensouls",
  blueprint: "samantha-provokes",
  soulId: "my-favorite-id",
  environment: {
    entityName: "Bob",
    emotions: ["angry", "upset"],
  },
})
ℹ️

See the Blueprints section on how to use environment variables in your blueprints.