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