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:
- 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. - 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.