Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/src/commands/_lib/apply/map-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ function mapAgent(
// provider keys + auth-profile credentials. The config row never holds
// cleartext at rest; the secret name is collected for the secrets gate.
config: Object.fromEntries(
Object.entries(p.config).map(([k, v]) => [
Object.entries(p.config ?? {}).map(([k, v]) => [
k,
resolveCredentialValue(v, required, env),
])
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/config/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,10 @@ export interface Platform {
/**
* Platform config (e.g. `{ botToken: secret("TELEGRAM_BOT_TOKEN") }`). Values
* are `secret(...)` refs or literal `$VAR` strings; `lobu apply` keeps the
* `$VAR` placeholder in the stored config and resolves it at egress.
* `$VAR` placeholder in the stored config and resolves it at egress. Optional —
* the `rest` (HTTP API) platform needs no config.
*/
config: Record<string, string | SecretRef>;
config?: Record<string, string | SecretRef>;
/** Declarative channel bindings (`"<teamId>/<channelId>"`); Slack only. */
channels?: string[];
}
Expand Down
201 changes: 200 additions & 1 deletion packages/cli/src/templates/AGENTS.md.tmpl
Original file line number Diff line number Diff line change
@@ -1 +1,200 @@
@TESTING.md
# {{PROJECT_NAME}} — Lobu project guide

This file is auto-loaded by your coding agent and is the source of truth for how
to build and run this Lobu project. Follow it — you do not need any separately
installed skill or plugin.

## What Lobu is

An open-source, event-sourced backend for AI agents:

- **Connectors** pull from external sources (GitHub, Slack, a website, a webhook,
a CSV) via scheduled **feeds** that collect incrementally — each run picks up
only what's new since the last (checkpointed), so nothing is re-ingested — and
emit **events**. When onboarding a user, explain this model in plain terms before
writing config so they understand what they're building.
- **Memory** turns events into a structured, append-only knowledge graph of typed
**entities** and **relationships**.
- **Agents** react to messages in real time and answer over chat platforms, HTTP,
and MCP.
- **Watchers** are scheduled prompts that read the graph and act (a daily digest,
inbound triage, etc.).

`lobu run` boots the whole thing (gateway + worker + memory) as one Node process
on http://localhost:{{GATEWAY_PORT}}.

## Prerequisites

Before `lobu run`, the host needs Node 22-24 (25+ is rejected — `isolated-vm` has
no build) and one LLM provider key in `.env`. Postgres is built in: `lobu run`
starts an embedded PostgreSQL (with `pgvector`); set `DATABASE_URL` only to use an
external Postgres instead. If a prerequisite is missing, stop and help the user
install it — don't work around it.

## Layout

- `lobu.config.ts` — the entire project as TypeScript. Source of truth; the only
file `lobu apply` reads.
- `agents/<id>/IDENTITY.md`, `SOUL.md`, `USER.md` — agent persona / behavior /
user context.
- `connectors/*.connector.ts` — custom data sources (only when no bundled
connector fits).
- `*.reaction.ts` — code a watcher runs after extraction (post to Slack, update
an entity).
- `skills/<name>/SKILL.md` — reusable capability bundles for the agent.
- `.env` — secrets (`DATABASE_URL`, provider keys, OAuth creds). Never commit real
values; never invent them.

## Config API

Author everything in `lobu.config.ts` with helpers from `@lobu/cli/config`:
`defineConfig`, `defineAgent`, `defineEntityType`, `defineRelationshipType`,
`defineWatcher`, `defineConnection`, `defineAuthProfile`, `secret`, plus
`connectorFromFile`, `reactionFromFile`, `skillFromFile`.

Read the complete, working reference before editing:
https://github.com/lobu-ai/lobu/blob/main/examples/lobu-crm/lobu.config.ts

### Agent

```ts
const agent = defineAgent({
id: "{{PROJECT_NAME}}",
// dir defaults to ./agents/<id> (where init scaffolds IDENTITY.md / SOUL.md / USER.md)
name: "{{PROJECT_NAME}}",
description: "<one sentence: what it does>",
providers: [
{ id: "anthropic", model: "anthropic/claude-...", key: secret("ANTHROPIC_API_KEY") },
],
// skills: [skillFromFile("./skills/<name>")],
// network: { allowed: ["api.example.com"] }, // egress allowlist
});
```

### Chat platforms (where people talk to it)

Where people message the agent is declared on the agent via `platforms`, NOT as a
`defineConnection` (connections are data sources). Chat-platform `config` values are
`secret(...)` refs resolved from `.env`. The `rest` platform exposes the HTTP Agent
API (POST to `/lobu/api/v1/agents/<id>/messages`) and takes an empty `config: {}`:

```ts
const agent = defineAgent({
id: "{{PROJECT_NAME}}",
// ...providers...
platforms: [
{ type: "slack", config: { botToken: secret("SLACK_BOT_TOKEN") } },
{ type: "rest", config: {} }, // HTTP API — no secret needed; always config: {}
// other chat types: telegram | discord | whatsapp | teams | google_chat
// Slack only: optional channels: ["<teamId>/<channelId>"]
],
});
```

### Entity types (what it remembers)

```ts
const ticket = defineEntityType({
key: "ticket",
name: "Ticket",
required: ["subject"],
properties: {
subject: { type: "string", "x-table-label": "Subject", "x-table-column": true },
status: { type: "string", enum: ["open", "closed"], "x-table-label": "Status", "x-table-column": true },
},
});
```

Each property is a JSON Schema fragment. `"x-table-column": true` surfaces it as a
column in the admin UI.

### Connection (a bundled connector — preferred)

```ts
const ghAuth = defineAuthProfile({ slug: "gh", connector: "github", authKind: "oauth_account", name: "GitHub" });

const gh = defineConnection({
slug: "github-main",
connector: "github",
name: "GitHub",
authProfile: ghAuth, // declare the profile as a const, then list it in defineConfig({ authProfiles })
config: { repo_owner: "you", repo_name: "repo" },
feeds: [{ feed: "issues", name: "Issues", schedule: "0 */6 * * *", config: { repo_owner: "you", repo_name: "repo" } }],
});
```

**Bundled connector keys** — use the exact key (Gmail is `google_gmail`, not
`gmail`): `github`, `google_gmail`, `google_calendar`, `microsoft_outlook`,
`website`, `rss`, `hackernews`, `reddit`, `x`, `linkedin`, `youtube`, `spotify`,
`producthunt`, `g2`, `capterra`, `glassdoor`, `trustpilot`, `revolut`, `gmaps`,
plus on-device connectors (`apple_health`, `apple_photos`, `apple_screen_time`,
`chrome*`, `local_directory`, `whatsapp_local`).

Each connector defines its own `config` keys and feed keys — don't guess them. If
you're unsure of a connector's config shape, ask the user or read its definition;
`npx @lobu/cli@latest connector run <key> --check` resolves and validates a config
without executing it.

If the source has no bundled connector, write `connectors/<name>.connector.ts`
(model it on `examples/lobu-crm/funnel-form.connector.ts` in the repo) and
reference it with `connectorFromFile("./connectors/<name>.connector.ts")`.

### Auth profiles & secrets

Integrations authenticate via `defineAuthProfile` (`oauth_account` = the user logs
in interactively; `oauth_app` = your own OAuth app, creds passed as `secret(...)`).
Every credential is a `secret("ENV_NAME")` placeholder resolved from `.env` — never
paste a real key into the config, and never fabricate one. If a secret is missing,
ask the user to add it to `.env`.

### Watcher (scheduled prompt) + reaction

```ts
const digest = defineWatcher({
agent,
slug: "daily-digest",
name: "Daily digest",
schedule: "0 9 * * *", // cron
prompt: "Summarize yesterday's tickets by root cause and post to Slack.",
extractionSchema: { type: "object", properties: { summary: { type: "string" } } },
// reaction: reactionFromFile<typeof reactionMod>("./daily-digest.reaction.ts"),
});
```

No `reaction` → the extracted data is written to memory. A `reaction` lets the
watcher act (post a message, update entities).

### Wire it together

```ts
export default defineConfig({
org: "<slug>",
orgName: "<Display name>",
agents: [agent],
entities: [ticket],
connections: [gh],
authProfiles: [ghAuth],
watchers: [digest],
});
```

## Commands

```bash
npx @lobu/cli@latest run # boot locally on http://localhost:{{GATEWAY_PORT}}
npx @lobu/cli@latest validate # check the config before running
npx @lobu/cli@latest login # auth the CLI (device-code flow)
npx @lobu/cli@latest apply # push this config to a Lobu org (cloud or self-host)
```

## Conventions

- **Never fabricate credentials, tokens, or API keys.** Ask the user; store them in
`.env` and reference them with `secret(...)`.
- **Pause at every real decision and ask the user** — don't guess what to build.
- **`events` is append-only** — never delete rows; supersede/tombstone instead.
- **Search before create** to avoid duplicate entities.
- Run `lobu validate` after editing the config; boot with `lobu run` and verify a
real message plus the resulting memory event before calling it done.

@TESTING.md
4 changes: 2 additions & 2 deletions packages/cli/src/templates/README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Lobu instance created with `@lobu/cli` v{{CLI_VERSION}}

## Quick Start

Lobu boots as a single Node process. By default `lobu run` uses local PGlite with pgvector enabled and stores data under `~/.lobu/data` (override with `LOBU_DATA_DIR`). Set `DATABASE_URL` in `.env` only when you want to use an external Postgres instead.
Lobu boots as a single Node process. By default `lobu run` starts an embedded PostgreSQL (with pgvector) and stores data under `~/.lobu/data` (override with `LOBU_DATA_DIR`). Set `DATABASE_URL` in `.env` only when you want to use an external Postgres instead.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Default embedded data path in docs appears incorrect.

This line says data lives under ~/.lobu/data and references LOBU_DATA_DIR, but runtime behavior indicates embedded DB resolves to a root and stores cluster data at .lobu/pgdata beneath it. Please align this sentence with actual lobu run behavior to prevent operator confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/templates/README.md.tmpl` at line 7, Update the README
template sentence to reflect actual `lobu run` behavior: state that the embedded
Postgres cluster data is created under the run root at `.lobu/pgdata` (not
`~/.lobu/data`), and clarify that `LOBU_DATA_DIR` points to the run root which
can be overridden; reference the `lobu run` command, the `LOBU_DATA_DIR` env
var, and the `.lobu/pgdata` path in the revised sentence.


```bash
# 1. Boot Lobu (gateway + workers + memory backend in one Node process)
Expand Down Expand Up @@ -33,7 +33,7 @@ Compare your `.env` with the latest `.env.example` in the [Lobu repo](https://gi

Edit `.env` to configure:

- `DATABASE_URL` — optional external Postgres connection string (with pgvector). If unset, `lobu run` uses local PGlite.
- `DATABASE_URL` — optional external Postgres connection string (with pgvector). If unset, `lobu run` uses its embedded PostgreSQL.
- `PUBLIC_GATEWAY_URL` — Public URL for OAuth callbacks
- `WORKER_ALLOWED_DOMAINS` — Allowed domains for worker network access

Expand Down
20 changes: 14 additions & 6 deletions packages/landing/src/components/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,24 @@ const snippets = snippetsManifest as LandingSnippets;

const EXAMPLE_BASE_URL = "https://github.com/lobu-ai/lobu/tree/main/examples";

const SETUP_PROMPT = `I want to build a Lobu agent.
const SETUP_PROMPT = `I want to build a Lobu agent with you. Lobu is an open-source, event-sourced backend for AI agents: connectors emit events, memory keeps a structured knowledge graph, and agents react in real time and run on a schedule. Set it up with me end to end.

1. Install the Lobu skill so you have the project conventions and tooling:
/plugin install lobu
1. Interview me, one question at a time. Wait for my answer before the next. Don't batch them, don't guess, and don't fake any credentials:
- What is the agent for? (one sentence)
- Who uses it: just me, my team, or each of my customers (multi-tenant)?
- What should it remember? (we'll model this as 1-3 entity types)
- Where does its data come from? Lobu has built-in connectors for Slack, Gmail, GitHub, Google Calendar, Outlook, websites, RSS, Reddit, X, LinkedIn, YouTube, Hacker News, Product Hunt, and more — or you can write a custom connector for any other source (an API, a webhook, a CSV). Tell me the source and I'll map it to a built-in connector or plan a custom one. Pick one to start.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace the em dash in landing copy.

Line 44 uses an em dash in user-facing text (...and more — or...), which violates landing copy rules. Replace it with a comma or period.

As per coding guidelines, "packages/landing/**/*.{ts,tsx,md}: No em dashes in user-facing text in landing copy".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/landing/src/components/LandingPage.tsx` at line 44, The landing copy
in the LandingPage component contains an em dash in the string fragment "and
more — or" which violates the no-em-dash rule for packages/landing; update the
user-facing text in the LandingPage component (search for the phrase "and more —
or" in LandingPage.tsx) and replace the em dash with a comma or period (e.g.,
"and more, or" or "and more. Or") to comply with the guideline.

- Where do people talk to it? (Slack, Telegram, Discord, WhatsApp, web/HTTP, or MCP)
- Anything on a schedule? (optional: one watcher, e.g. a daily summary)
- Which LLM provider key do I have: Anthropic, OpenAI, or Z.ai?

2. Walk me through the skill's onboarding interview (it asks what the agent should do, who uses it, where data comes from, where I'll talk to it, what should run on a schedule). Pause at every real decision and ask me, don't fake credentials, don't guess.
2. Scaffold it: check my Node is 22-24 (Lobu rejects 25+; help me switch if not), then run npx @lobu/cli@latest init with the name and the provider from above. Postgres is built in — lobu run starts an embedded one, so don't ask me for a database unless I want an external Postgres (then I set DATABASE_URL). Read the AGENTS.md it writes (your guide to the config API: the define* helpers, connectors, auth, watchers, memory), and read examples/lobu-crm/lobu.config.ts before writing any connection, watcher, or reaction so you match the real field names instead of guessing. Then, before writing config, explain to me in plain terms how Lobu will work for my case: how the connector collects my data incrementally (feeds run on a schedule and only pull what's new since the last run — no re-ingesting), how each item becomes an event that memory turns into the entities above, and how both the watcher and the chat read that memory. Keep it short.

3. Scaffold the project per my answers (lobu.config.ts plus any connector, reaction, and skill files it references), boot it locally, send a test message via the chosen channel, and show me the memory event that was written.
3. Build it from my answers: edit lobu.config.ts plus any connector, reaction, and skill files it needs. Then tell me in one go every secret you'll need (API keys, OAuth client id/secret, bot tokens) and we'll add them to .env together as secret(...) placeholders. Never invent one, and for OAuth sources authorize the account in the admin UI rather than hand-crafting a token.

Lobu is an open-source event-sourced backend for AI agents: connectors emit events, memory keeps the structured record, agents react in real time and dream on cron. Repo: https://github.com/lobu-ai/lobu. Docs: https://lobu.ai/docs/`;
4. Run and verify: run npx @lobu/cli@latest validate and fix any errors, then boot with npx @lobu/cli@latest run. Send a test message on the channel I chose, trigger the data source manually (don't wait on a poll or cron), and show me the memory event that was written plus the admin UI at http://localhost:8787.

Repo: https://github.com/lobu-ai/lobu. Docs: https://lobu.ai/docs/`;

const GITHUB_URL = "https://github.com/lobu-ai/lobu";

Expand Down
2 changes: 1 addition & 1 deletion packages/owletto
Loading