Skip to content

Commit abb883b

Browse files
committed
feat: refactor to a core lib
1 parent 4a7ef30 commit abb883b

58 files changed

Lines changed: 1437 additions & 12939 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 0 additions & 11 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,3 @@ jobs:
3232

3333
- name: Run tests
3434
run: cargo test --workspace --all-targets --quiet
35-
36-
ui-build:
37-
name: UI Build
38-
runs-on: ubuntu-latest
39-
steps:
40-
- uses: actions/checkout@v4
41-
42-
- name: Setup Node.js
43-
uses: actions/setup-node@v4
44-
with:
45-
node-version: '20'
46-
cache: 'npm'
47-
cache-dependency-path: 'rain-engine-ui/package-lock.json'
48-
49-
- name: Install dependencies
50-
working-directory: ./rain-engine-ui
51-
run: npm ci || npm install
52-
53-
- name: Build UI
54-
working-directory: ./rain-engine-ui
55-
run: npm run build

Cargo.lock

Lines changed: 2 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
members = [
33
"rain-engine-blob",
44
"rain-engine-channels",
5-
"rain-engine-cli",
65
"rain-engine-cognition",
76
"rain-engine-core",
8-
"rain-engine-gateway",
97
"rain-engine-ingress",
108
"rain-engine-macros",
119
"rain-engine-memory",
@@ -46,7 +44,6 @@ headless_chrome = "1.0.21"
4644
tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time", "net", "process", "io-util", "signal"] }
4745
tokio-util = "0.7"
4846
tower = { version = "0.5", features = ["util"] }
49-
tower-http = { version = "0.6", features = ["cors"] }
5047
tracing = "0.1"
5148
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
5249
url = "2"
@@ -56,7 +53,6 @@ wat = "1"
5653
quote = "1"
5754
proc-macro2 = "1"
5855
redis = { version = "0.32.7", features = ["aio", "tokio-comp"] }
59-
serde_yaml = "0.9"
6056
typeshare = "1"
6157

6258
[package]
@@ -65,7 +61,7 @@ version.workspace = true
6561
edition.workspace = true
6662
license.workspace = true
6763
description = "An event-sourced Rust kernel for building durable AI agent systems"
68-
repository = "https://github.com/peter-steinberger/rain-engine" # Example placeholder
64+
repository = "https://github.com/Adriftdev/rain-engine"
6965

7066
[features]
7167
default = ["server", "client", "sqlite", "gemini"]

README.md

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# RainEngine
22

3-
RainEngine is a self-improving, event-sourced Rust kernel for building durable
4-
AI agent systems.
3+
RainEngine is a self-improving, event-sourced Rust library workspace for
4+
building durable AI agent systems.
55

66
The core idea is simple: an agent is a state machine backed by an append-only
77
ledger. Every webhook, human message, scheduled wake, approval, delegation
@@ -19,23 +19,19 @@ visible, auditable, and never grants new power silently.
1919
- `rain-engine-core`: provider-neutral event kernel, domain models, policies, traits, ledger projections, and in-memory stores
2020
- `rain-engine-cognition`: optional planners, task routing, review policy, wake policy, and reflection policy
2121
- `rain-engine-memory`: ledger-backed exact replay, recent working sets, graph lookup, and simple semantic retrieval
22-
- `rain-engine-runtime`: reference Axum runtime that parses events and owns the advance loop
23-
- `rain-engine-gateway`: batteries-included gateway that wires runtime, built-in skills, channels, and wake handling
24-
- `rain-engine-ui`: Solid/Vite control room for live session inspection and human approval
25-
- `rain-engine-client`: Rust client for gateway integrations
26-
- `sdk/typescript`: TypeScript SDK for browser and Node clients
22+
- `rain-engine-runtime`: reference Axum runtime library that parses events and owns the advance loop
23+
- `rain-engine-client`: Rust client for runtime integrations
2724
- `rain-engine-ingress`: shared event envelope and Valkey Streams worker utilities
2825
- `rain-engine-blob`: local and in-memory blob stores for multimodal attachments
2926
- `rain-engine-wasm`: Wasmtime executor for untrusted skills with explicit host capabilities
3027
- `rain-engine-macros`: `#[derive(SkillManifest)]` for typed skill manifests
31-
- `rain-engine-skills`: built-in trusted native skills for local development and gateway deployments
28+
- `rain-engine-skills`: built-in trusted native skills for local development and runtime-backed deployments
3229
- `rain-engine-channels`: channel adapters that translate external messages into kernel events
3330
- `rain-engine-provider-gemini`: Gemini REST provider with multimodal content, parallel tool calls, and cache metadata
3431
- `rain-engine-openai`: OpenAI-compatible baseline provider
3532
- `rain-engine-store-pg`: Postgres append-only ledger store
3633
- `rain-engine-store-sqlite`: SQLite ledger store for local development and tests
3734
- `rain-engine-store-valkey`: Valkey coordination claims and short-lived scratchpad storage
38-
- `rain-engine-cli`: declarative `agent.yaml` validation and runtime bootstrapping
3935

4036
## Architecture
4137

@@ -101,37 +97,13 @@ Guardrails are explicit:
10197
- regressions create rollback records and remove the overlay from future
10298
effective policy
10399

104-
## Control Room
100+
## Library Surface
105101

106-
RainEngine includes a web control room for operating the kernel through the
107-
server-client contract. The UI connects to the gateway, streams session updates
108-
over server-sent events, and renders the ledger as a live workbench:
109-
110-
- session status, provider capabilities, and registered tools
111-
- human input and assistant outcomes
112-
- reasoning plans, tool execution graphs, checkpoints, validation failures, and
113-
tool results
114-
- approval checkpoints with exact arguments and structured approve/deny actions
115-
- learning events, active policy overlays, reflections, and rollback history
116-
- projected goals, tasks, observations, artifacts, and raw records
117-
118-
Start the development gateway:
119-
120-
```bash
121-
cargo run -p rain-engine-cli -- dev
122-
```
123-
124-
Then run the UI:
125-
126-
```bash
127-
cd rain-engine-ui
128-
npm install
129-
npm run dev
130-
```
131-
132-
The runtime exposes `GET /capabilities`, `GET /sessions/{id}/view`, and
133-
`GET /sessions/{id}/stream` so other clients can build the same control surface
134-
without depending on UI internals.
102+
RainEngine intentionally stops at reusable libraries. There are no first-party
103+
CLI, standalone daemon binary, or browser UI surfaces in this repository. The runtime,
104+
client, ingress, store, and provider crates are intended to be embedded in
105+
other systems that provide their own process model, configuration UX, and
106+
operator tooling.
135107

136108
## State Model
137109

@@ -151,14 +123,11 @@ must be rebuildable from stored records.
151123

152124
- Embedded SQLite flow: [embedded_sqlite.rs](/Users/adrift/projects/rain-engine/rain-engine-runtime/examples/embedded_sqlite.rs)
153125
- Runtime bootstrap config: [runtime_postgres.rs](/Users/adrift/projects/rain-engine/rain-engine-runtime/examples/runtime_postgres.rs)
154-
- Customer support deployment sketch: [customer_support_agent.rs](/Users/adrift/projects/rain-engine/rain-engine-cli/examples/customer_support_agent.rs)
155-
- Declarative deployment config: [agent.yaml](/Users/adrift/projects/rain-engine/rain-engine-cli/examples/agent.yaml)
156126

157127
## Verification
158128

159129
```bash
160130
cargo fmt
161131
cargo clippy --workspace --all-targets
162132
cargo test --workspace --all-targets --quiet
163-
cd rain-engine-ui && npm run build
164133
```

rain-engine-channels/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ license.workspace = true
66

77
[dependencies]
88
async-trait.workspace = true
9+
axum.workspace = true
910
rain-engine-client = { path = "../rain-engine-client" }
1011
reqwest = { workspace = true }
1112
serde.workspace = true

rain-engine-channels/src/discord.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Discord bot adapter using REST gateway interactions.
1+
//! Discord bot adapter using runtime delivery plus Discord Gateway events.
22
//!
33
//! Requires `DISCORD_BOT_TOKEN` to be set.
44
//! This adapter uses the Discord Gateway (WebSocket) for receiving messages
@@ -21,7 +21,7 @@ pub struct DiscordAdapter {
2121
impl DiscordAdapter {
2222
pub fn new(token: String, config: ChannelConfig) -> Self {
2323
Self {
24-
engine_client: RainEngineClient::new(&config.gateway_url)
24+
engine_client: RainEngineClient::new(&config.runtime_url)
2525
.expect("failed to init client"),
2626
client: reqwest::Client::new(),
2727
token,

rain-engine-channels/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use async_trait::async_trait;
1313
/// Configuration for a channel adapter.
1414
#[derive(Debug, Clone)]
1515
pub struct ChannelConfig {
16-
/// Base URL of the RainEngine gateway.
17-
pub gateway_url: String,
16+
/// Base URL of the RainEngine runtime HTTP surface.
17+
pub runtime_url: String,
1818
/// Session ID to use (or derive from platform user ID).
1919
pub default_session_prefix: String,
2020
}

rain-engine-channels/src/slack.rs

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
77
use crate::{ChannelAdapter, ChannelConfig};
88
use async_trait::async_trait;
9+
use axum::{Json, Router, extract::State, http::StatusCode, routing::post};
910
use rain_engine_client::RainEngineClient;
1011
use serde::Deserialize;
12+
use serde_json::json;
13+
use std::sync::Arc;
1114
use tracing::{error, info, warn};
1215

1316
#[derive(Debug, Clone)]
@@ -30,7 +33,7 @@ impl SlackAdapter {
3033
config: ChannelConfig,
3134
) -> Self {
3235
Self {
33-
engine_client: RainEngineClient::new(&config.gateway_url)
36+
engine_client: RainEngineClient::new(&config.runtime_url)
3437
.expect("failed to init client"),
3538
client: reqwest::Client::new(),
3639
bot_token,
@@ -124,22 +127,57 @@ impl ChannelAdapter for SlackAdapter {
124127
port = self.listen_port,
125128
"Slack adapter started — listening for Events API"
126129
);
130+
warn!("Slack request signature verification is not enforced yet");
127131

128-
// In production this would spin up a small axum server to receive
129-
// Slack Event API POSTs. For now, the structural skeleton is in place.
130-
// The handle_event_message method is fully wired and ready.
131-
//
132-
// Integration pattern:
133-
// 1. Slack sends POST to http://your-server:{port}/slack/events
134-
// 2. We parse the SlackEventPayload
135-
// 3. For url_verification: return { challenge }
136-
// 4. For event_callback with message type: call handle_event_message
137-
warn!(
138-
"Slack adapter: Events API HTTP listener not yet started. Use handle_event_message() for integration."
139-
);
132+
let app = Router::new()
133+
.route("/slack/events", post(handle_events))
134+
.with_state(Arc::new(self.clone()));
135+
136+
let listener = match tokio::net::TcpListener::bind(("0.0.0.0", self.listen_port)).await {
137+
Ok(listener) => listener,
138+
Err(err) => {
139+
error!("Slack adapter failed to bind: {err}");
140+
return;
141+
}
142+
};
143+
144+
if let Err(err) = axum::serve(listener, app)
145+
.with_graceful_shutdown(async move { cancel.cancelled().await })
146+
.await
147+
{
148+
error!("Slack adapter listener error: {err}");
149+
}
140150

141-
// Keep alive until cancelled
142-
cancel.cancelled().await;
143151
info!("Slack adapter shutting down");
144152
}
145153
}
154+
155+
async fn handle_events(
156+
State(adapter): State<Arc<SlackAdapter>>,
157+
Json(payload): Json<SlackEventPayload>,
158+
) -> Result<Json<serde_json::Value>, StatusCode> {
159+
match payload.r#type.as_str() {
160+
"url_verification" => {
161+
let challenge = payload.challenge.unwrap_or_default();
162+
Ok(Json(json!({ "challenge": challenge })))
163+
}
164+
"event_callback" => {
165+
if let Some(event) = payload.event {
166+
let is_supported_message =
167+
event.r#type == "message" || event.r#type == "app_mention";
168+
if is_supported_message
169+
&& let (Some(channel), Some(user), Some(text)) = (
170+
event.channel.as_deref(),
171+
event.user.as_deref(),
172+
event.text.as_deref(),
173+
)
174+
&& event.bot_id.is_none()
175+
{
176+
adapter.handle_event_message(channel, user, text).await;
177+
}
178+
}
179+
Ok(Json(json!({ "ok": true })))
180+
}
181+
_ => Ok(Json(json!({ "ok": true }))),
182+
}
183+
}

rain-engine-channels/src/telegram.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub struct TelegramAdapter {
1919
impl TelegramAdapter {
2020
pub fn new(token: String, config: ChannelConfig) -> Self {
2121
Self {
22-
engine_client: RainEngineClient::new(&config.gateway_url)
22+
engine_client: RainEngineClient::new(&config.runtime_url)
2323
.expect("failed to init client"),
2424
client: reqwest::Client::new(),
2525
token,

0 commit comments

Comments
 (0)