Skip to main content

Hello world example

A practical example to help you write your first application using Orbits.

Scope

In this example, we’ll create a simple “hello-world” application. The behavior is straightforward:

  • After 10 AM, say hello.
  • Say hello only once per day.
  • Say goodbye when the agent is uninstalled.

Prerequisites

Clone this repository

cd samples/hello-world
  • Install node.js dependencies: npm i

Project Structure

├── src/
│ └── orbits/
│ └── orbi.ts # main orchestration script
│ ├── greetings-agent.ts # the agent that orchestrates the lifecycle of greetings
│ ├── hello-action.ts # an action that waits 10 am and outputs hello
│ └── hello-workflow.ts # a workflow that takes a name as parameter and outputs "hello ${name}"
├── package.json
└── README.md

The Hello Action

src/orbits/hello-action.ts
import { Action, ActionState } from '@orbi-ts/core';

export class HelloAction extends Action {
main() {
this.internalLog('Hello 👋', { level: 'info' });
this.setResult('Hello');
return ActionState.SUCCESS;
}
}

This simple action logs a greeting and completes immediately.

The main() method is the entry point for the action and is guaranteed to run exactly once — even across retries or resumptions. This ensures predictable and idempotent side effects.

The Hello Workflow

src/orbits/hello-workflow
import { Workflow } from '@orbi-ts/core';
import { HelloAction } from './hello-action.ts';

export class HelloWorkflow extends Workflow {
declare IResult: string;

declare IArgument: {
name: string;
};

async define() {
const prefix = await this.do('hello', new HelloAction());
const suffix = await this.do('name', () => {
return Promise.resolve(this.argument.name);
});
return `${prefix} ${suffix}`;
}
}

Type declarations

declare;
IResult: string;

declare;
IArgument: {
name: string;
}
  • IResult defines the return type of the workflow.
  • IArgument defines the expected input arguments.
const result = await this.do('hello', new HelloWorkflow()); // `result` is typed as string
new HelloWorkflow().setArgument({ name: 'Alice' }); // type-checked argument

Define() method

The define() method describes the logic of the workflow and must use this.do() for any asynchronous operations. Directly using raw promises is discouraged.

Example illustrating execution behavior:

async define() {
console.log("Called multiple times");
await this.do("log", () => {
console.log("Called only once");
return Promise.resolve();
});
}

This approach aligns with the SAGA principle. See the Orbits Core documentation for more on this.do()

Result

At the end of define(), we return a string which becomes the result of the workflow.

The Greetings Agent

src/orbits/greeting-agent.ts
export class GreetingAgent extends Agent {
declare IResult: string;

declare IArgument: {
name: string;
date: string;
} & Agent['IArgument'];

identity() {
return `${this.argument.name}-${this.argument.date}`;
}

async defineInstall() {
const greeting = await this.do(
'greeting',
new HelloWorkflow().setArgument({
name: this.argument.name,
})
);
await this.do('display-greeting', () => {
this.internalLog(`${greeting} 👋👋👋`, { level: 'info' });
return Promise.resolve();
});
}

async defineUpdate() {
//do nothing
this.internalLog(`(I have already seen you.)👻`, { level: 'info' });
}

async defineUninstall() {
await this.do('display-goodbye', () => {
this.internalLog(
`Goodbye my dear friend ${this.argument.name} 👋👋👋`,
{ level: 'info' }
);
return Promise.resolve();
});
}
}

Type declarations

declare IResult: string;

declare IArgument: {
name: string;
date: string
} & Agent["IArgument"]

Same as for workflows, this enables type checking and autocompletion.

Identity method

The identity() method uniquely identifies a agent instance. If two instances share the same identity, they refer to the same real-world object and will share state.

identity() {
return `${this.argument.name}-${this.argument.date}`;
}

That means that a greeting agent is unique by name and by date.

Install hook

Runs once when the agent is first created.

async defineInstall() {
const greeting = await this.do(
'greeting',
new HelloWorkflow().setArgument({
name: this.argument.name,
})
);
await this.do('display-greeting', () => {
this.internalLog(`${greeting} 👋👋👋`, { level: 'info' });
return Promise.resolve();
});
}

Update hook

async defineUpdate() {
//do nothing
this.internalLog(`(I have already seen you.)👻`, { level: 'info' });
}

Runs every time the agent is reused with the same identity.

Uninstall hook

async defineUninstall() {
await this.do('display-goodbye', () => {
this.internalLog(`Goodbye my dear friend ${this.argument.name} 👋👋👋`, { level: 'info' });
return Promise.resolve();
});
}

To trigger it:

    new GreetingAgent().setArgument(...).setCommand('Uninstall');

Go further: cycle hook

We don't use it in our sample, but agents offer the possibility to launch a cycle command at fixed intervals. This can be useful for recurring tasks like polling, cleanup, or reporting. Please see the documentation to learn more.

Orbi.ts

In orbi.ts, we do:

src/orbits/orbi.ts
import { Action, ActionRuntime, ActionState } from '@orbi-ts/core';
import { GreetingAgent } from './greetings-agent.ts';

ActionRuntime.activeRuntime.waitForBootstrap.then(async () => {
const greetingOfTheDay = new GreetingAgent().setArgument({
name: 'John Doe',
date: String(new Date().toISOString().split('T')[0]),
});
greetingOfTheDay.save();
await Action.trackActionAsPromise(greetingOfTheDay, [
ActionState.SUCCESS,
ActionState.ERROR,
]);

const greetingOfTheDay2 = new GreetingAgent().setArgument({
name: 'John Doe',
date: String(new Date().toISOString().split('T')[0]),
});
greetingOfTheDay2.save();
await Action.trackActionAsPromise(greetingOfTheDay2, [
ActionState.SUCCESS,
ActionState.ERROR,
]);
});

We launch the same Agent twice. This shows evidence that:

  • during the first run, you will see greeting in the logs
  • during the second run, you will only see the "I have already seen you" in the logs

Running the sample

Run the Script

export ORBITS_DB__MONGO__URL="your-mongo-cluster"
npx tsx src/orbits/orbi.ts

Expected Output

  • First run: logs the greeting
  • Subsequent runs: logs "I have already seen you"

Run the uninstall command

Edit orbi.ts:

src/orbits/orbi.ts
ActionRuntime.activeRuntime.waitForBootstrap.then(async () => {
const greetingOfTheDay = new GreetingAgent().setArgument({
name: "John Doe",
date: String(new Date().toISOString().split("T")[0])
}).setCommand("Uninstall")
greetingOfTheDay.save();
)

Then run:

export ORBITS_DB__MONGO__URL="your-mongo-cluster"
npx tsx src/orbits/orbi.ts

You should see the goodbye message in the logs.

Using the CLI

Instead of running the script above, you can directly run the agent from the command line using the CLI:

  • running the agent
orbits-cli actions run MyGreetings name='John Doe' date='01-01-01' -f src/orbits/greeting-agent.ts -l
  • uninstalling the agent
orbits-cli actions run MyGreetings name='John Doe' date='01-01-01' commandName=Uninstall -f src/orbits/greeting-agent.ts -l

Next Steps

What's Next?

Here are three recommended next steps to continue your journey:

  1. 🧩 Cross-account cdk examples - Show how to use infrastructure templates in conjunction with orbits
  2. ⚙️ Core concepts - Master the fundamental architecture principles and design patterns that power Orbits
  3. 🛤️ Guides - Explore hands-on tutorials ranging from beginner-friendly to advanced implementation techniques