The execution layer for AgentLily instances — autonomous AI agents that manage finance on the Stellar network.
agentlily-runtime is the TypeScript runtime that runs an AgentLily: an autonomous agent owned by Lily Protocol, the agent-finance infrastructure being built on Stellar. An AgentLily's job is to act on behalf of its controller — provisioning, preparing, and (soon) executing Stellar wallet and payment tasks with observable, auditable steps.
wallet.prepare_paymenttool (shipped today) — a real, typed tool an AgentLily invokes to prepare a payment for its wallet: it validates the wallet and amount, defaults the asset to nativeXLM, and returns a simulated Stellar transaction stub (stellar-stub-<taskId>-<walletId>) — no live network call, safe for contributors to extend toward real submission.- Payment-aware action boundary —
src/actions/shows how wallet/payment actions are structured; the scaffolding for executing against the live Stellar network (via Lily backend + Soroban contracts) is intentionally open contributor work. - Event-driven & auditable — every task and tool invocation emits runtime events so Stellar finance actions are traceable from intent → prepared transaction stub.
┌─────────────────────────────────────────────────────────────┐
│ AgentLily (autonomous finance agent) │
│ AgentRuntime ── tasks ── tools ── events ── memory │
│ └ wallet.prepare_payment → validated Stellar XLM stub │
└──────────────────────────┬──────────────────────────────────┘
│ future: execute via Lily Protocol API
▼
┌─────────────────────────────────────────────────────────────┐
│ Lily Protocol on Stellar │
│ backend API · Soroban contracts · Stellar wallet/payments │
└─────────────────────────────────────────────────────────────┘
This repository is intentionally designed as an open-source-ready runtime foundation, not a completed runtime product. It provides:
- A modular TypeScript runtime architecture
- One real happy-path execution flow for contributors to study and extend
- A shipped, Stellar-flavored
wallet.prepare_paymenttool - Strict typing, tests, linting, and CI scaffolding
- Clear extension points for unfinished systems
The current implementation demonstrates a narrow, credible runtime path:
- Create an
AgentRuntime - Start the runtime and register tools (including
wallet.prepare_payment) - Build a runtime context for a task
- Execute a task through the task runner and action executor
- Invoke a typed tool
- Persist lightweight in-memory task history (or durable JSON file history via memoryStoragePath)
- Emit runtime events and structured log entries
This gives contributors a working reference path without locking the project into premature architecture.
The following areas are scaffolded with interfaces, types, or placeholders and are expected to become contributor work:
- Live Stellar network execution — turning prepared payment stubs into submitted transactions through the Lily backend (agent controllers approve first)
- Wallet-aware and payment-aware actions beyond the prep flow
- Persistent database and vector storage backends (basic file-based JSON persistence is supported via JsonFileMemoryStore)
- Model provider integrations (an
OpenAICompatibleModelProviderscaffold is available for experimentation; note that it is scaffolded and intentionally not production-complete) - Runtime policy engines and approval flows
- Long-running orchestration and scheduling
- Distributed execution and durable coordination
- Identity-aware execution logic
- Rich tracing, metrics, and production observability
src/
actions/ Minimal action execution flow + wallet.prepare_payment
agents/ Agent instance lifecycle scaffolding
errors/ Typed runtime errors
events/ Runtime event model and event bus
guards/ Runtime assertions and guardrails
logger/ Structured logger abstraction
memory/ In-memory store plus storage interface
providers/ Model/provider abstraction layer
runtime/ Bootstrap, context, and runtime composition
state/ Runtime state interface
tasks/ Task runner and task types
tools/ Tool contracts and registry
tests/ Foundation and happy-path tests
agentlily-runtime declares "engines": { "node": ">=20" }. The CI workflow actively verifies formatting, linting, typechecking, and the full test suite across a matrix of supported Node.js LTS lines:
- Node.js 20 (Declared baseline LTS floor)
- Node.js 22 (Active LTS)
npm install
npm run build
npm run testExample:
import { AgentRuntime } from "@lily-protocol/agentlily-runtime";
const runtime = new AgentRuntime({
runtimeId: "local-dev"
});
runtime.registerTool({
name: "echo",
description: "Returns a string payload for test execution",
async execute(input) {
return { echoed: String(input.payload.message ?? "") };
}
});
await runtime.start();
const result = await runtime.executeTask({
agentId: "agent-demo",
taskId: "task-001",
toolName: "echo",
input: "Send a greeting",
payload: { message: "hello lily" }
});
console.log(result.output);The shipped wallet.prepare_payment tool lets an AgentLily prepare a payment for one of its wallets without touching the live network:
import {
AgentRuntime,
createPaymentPrepTool
} from "@lily-protocol/agentlily-runtime";
const runtime = new AgentRuntime({ runtimeId: "agentlily-pay" });
runtime.registerTool(createPaymentPrepTool());
await runtime.start();
const prepared = await runtime.executeTask({
agentId: "agentlily_treasury",
taskId: "pay-001",
toolName: "wallet.prepare_payment",
input: "Prepare 25 XLM payment",
payload: {
walletId: "wallet_treasury",
amount: "25.00",
assetCode: "XLM",
memo: "monthly rebalance"
}
});
// prepared.output → {
// status: "prepared",
// transactionStubId: "stellar-stub-pay-001-wallet_treasury",
// assetCode: "XLM",
// amount: "25.00",
// isSimulated: true,
// ...
// }agentlily-runtime exposes an event system via RuntimeEventBus to support observability, audit logs, and tracing adapters.
| Event Name | Description | Key Payload Fields |
|---|---|---|
runtime.started |
Emitted once when runtime.start() succeeds |
runtimeId, occurredAt |
runtime.stopped |
Emitted when runtime.stop() completes |
runtimeId, occurredAt |
runtime.task.received |
Emitted when a task is accepted for execution | runtimeId, taskId, agentId |
runtime.task.completed |
Emitted when a task executes successfully | runtimeId, taskId, agentId, toolName, durationMs |
runtime.task.failed |
Emitted when task execution fails | runtimeId, taskId, agentId, reason |
runtime.tool.invoked |
Emitted when an individual tool action is invoked | runtimeId, taskId, agentId, toolName, invokedAt |
runtime.internal.error |
Emitted when an event listener throws an unhandled error | eventName, errorMessage, occurredAt |
You can inject a custom RuntimeEventBus during initialization or subscribe directly via runtime.eventBus:
import {
AgentRuntime,
RuntimeEventBus
} from "@lily-protocol/agentlily-runtime";
const eventBus = new RuntimeEventBus();
// Subscribe to task completion and failure events
const unsubscribeCompleted = eventBus.on("runtime.task.completed", (event) => {
console.log(
`Task ${event.payload.taskId} completed in ${event.payload.durationMs}ms`
);
});
const unsubscribeFailed = eventBus.on("runtime.task.failed", (event) => {
console.error(`Task ${event.payload.taskId} failed: ${event.payload.reason}`);
});
// Single-fire listener
eventBus.once("runtime.started", (event) => {
console.log(`Runtime started at ${event.payload.occurredAt}`);
});
const runtime = new AgentRuntime({
runtimeId: "monitored-runtime",
eventBus
});
await runtime.start();
// Unsubscribe when no longer needed
unsubscribeCompleted();
unsubscribeFailed();For persistent task history across runtime restarts, configure memoryStoragePath in RuntimeOptions. When supplied, AgentRuntime initializes a JsonFileMemoryStore backing instance instead of the default ephemeral InMemoryMemoryStore.
import { AgentRuntime } from "@lily-protocol/agentlily-runtime";
const runtime = new AgentRuntime({
runtimeId: "persistent-runtime",
memoryStoragePath: "./data/task-history.json"
});Each entry appended to the storage file satisfies the MemoryEntry interface:
| Field | Type | Description |
|---|---|---|
agentId |
string |
ID of the agent associated with the task |
taskId |
string |
Unique identifier of the task |
input |
string |
Input prompt or command given to the task |
output |
unknown |
Tool execution output or result |
recordedAt |
string |
ISO 8601 timestamp of when the entry was written |
- File Rewrites:
JsonFileMemoryStorereads and rewrites the entire JSON array on each append (flush()), making it suitable for development, testing, or low-throughput scenarios rather than high-frequency production pipelines. - No Inherent Capacity Limit: Unlike
InMemoryMemoryStore,JsonFileMemoryStorecurrently does not enforce global FIFO eviction or per-agent capacity limits; entries grow monotonically unless cleared manually viaclear(). - Multi-Process Concurrency: Concurrent writes across multiple Node.js processes targeting the same file path without external file locking may cause race conditions or lost updates.
npm run buildcompiles the librarynpm run lintruns ESLintnpm run typecheckruns TypeScript in no-emit modenpm run testruns Vitest with coveragenpm run verifyruns formatting, linting, typecheck, and tests
The repo is designed so contributions add depth without collapsing extension points. Examples:
- Add a new memory backend that implements
MemoryStore - Introduce runtime policies around tool allowlists
- Add an event sink or tracing adapter
- Implement a model provider adapter with tests
- Expand task lifecycle states beyond the current happy path
- Add a
wallet.sign/wallet.executetool path that hands a prepared Stellar stub to the Lily backend for authorization and submission
Maintainers can immediately create issues around:
- Provider adapters
- Runtime policies
- Persistent storage
- Wallet-aware execution boundaries
- Observability
- Documentation examples
- Test matrix expansion
The backlog section in the final delivery summary from this setup provides a ready-made issue starter list.