Skip to content

Commit 3479c84

Browse files
committed
init
1 parent 4690c81 commit 3479c84

280 files changed

Lines changed: 47770 additions & 0 deletions

File tree

Some content is hidden

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

.cargo/config.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[build]
2+
rustflags = [
3+
"-D", "warnings", # all warnings are errors
4+
"-D", "clippy::unwrap_used", # no unwrap() in library code
5+
"-D", "clippy::expect_used", # no expect() in library code (use thiserror)
6+
"-D", "clippy::panic", # no panic!() in library code
7+
]
8+
9+
# Ban native-tls to enforce rustls across all transitive dependencies.
10+
# Any crate pulling native-tls will get this stub, which fails at compile time.
11+
[patch.crates-io]
12+
native-tls = { path = "vendor/native-tls-stub" }

.claude/phases/phase-1a.md

Lines changed: 367 additions & 0 deletions
Large diffs are not rendered by default.

.claude/phases/phase-1b.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Phase 1b — Bot Foundations
2+
3+
> Source: `docs/current-arch/ARCHITECTURE.md` §3, §6.9, §14.1-14.2
4+
> Depends on: Phase 1a complete
5+
6+
## Goal
7+
8+
Classical bot runtime with deterministic command routing. First chat
9+
connector (Telegram). No AI. Users talk to the bot through Telegram and
10+
get the same automation they configured in Phase 1a, plus interactive
11+
commands.
12+
13+
## Two Sub-Milestones
14+
15+
**1b-i: Bot core** — testable without any chat platform.
16+
**1b-ii: First chat connector** — Telegram makes the bot reachable.
17+
18+
## Milestone 1b-i: springtale-bot
19+
20+
The bot runtime lives in `crates/springtale-bot/`. It depends on core,
21+
crypto, connector, store, and transport.
22+
23+
**How the event loop works:**
24+
25+
```rust
26+
pub async fn run(bot: &Bot) -> Result<()> {
27+
loop {
28+
tokio::select! {
29+
Some(msg) = bot.connector_events.recv() => {
30+
// Route through command router
31+
// On error: log + continue (NEVER propagate ? — kills the loop)
32+
}
33+
Some(trigger) = bot.rule_events.recv() => {
34+
// Evaluate and dispatch through rule engine
35+
}
36+
Some(job) = bot.job_events.recv() => {
37+
// Execute scheduled job
38+
}
39+
}
40+
}
41+
}
42+
```
43+
44+
**Critical design detail:** The event loop must NOT use `?` on individual
45+
message processing. A single bad message must not crash the bot. Log the
46+
error, continue to next event. This was flagged in the architecture audit.
47+
48+
**Command router (no AI):**
49+
- `router::prefix` — exact prefix match: `/search`, `/help`, `/remind`, `/status`
50+
- `router::pattern` — regex and keyword matching for non-slash triggers
51+
- `router::alias` — user-defined aliases persisted in SQLite (e.g., `/s` -> `/search`)
52+
- `router::fallback` — no match returns "Unknown command. Try /help" (AI fallback added in Phase 2a)
53+
- Router is a pure function: `(message_text) -> RouteResult::Command(name, args) | RouteResult::NoMatch`
54+
55+
**Handler dispatch:**
56+
- `handler::registry` — HashMap of command name -> handler function
57+
- `handler::builtin``/help` (list commands), `/status` (bot health), `/rules` (list active rules), `/connectors` (list installed)
58+
- `handler::connector` — generic handler: connector name + action derived from command. When connector-presearch is installed, `/search <query>` auto-registers.
59+
60+
**How auto-registration works:** When a connector is installed via
61+
`springtale connector install`, the bot reads its `actions()` and
62+
registers each as a command. `connector-presearch` with action `search`
63+
becomes `/search`. `connector-github` with action `create_issue` becomes
64+
`/github create_issue`. Users create aliases for convenience.
65+
66+
**Conversation state:**
67+
- `state::session` — per `(user_id, channel_id)` key in SQLite. Tracks what the bot last said, what it's waiting for (multi-step flows). Row-level isolation: no cross-user state access.
68+
- `state::prefs` — user preferences: timezone, language, notification settings. Persisted in SQLite. User manages via `/prefs set timezone America/New_York`.
69+
- `state::persona` — bot persona config: name, response tone, template library. Loaded from `springtale.toml`. Not user-configurable (admin sets it).
70+
71+
**Persistent memory:**
72+
- `memory::persistent` — SQLite-backed. Structured typed schemas, not arbitrary strings. Encrypted at rest via vault.
73+
- `memory::context` — sliding window of recent conversation per user. Configurable size (default: 50 messages).
74+
- `memory::compaction` — Phase 1b: simple truncation (drop oldest). Phase 2a: AI summarization when adapter is plugged in.
75+
76+
**Bot identity:**
77+
- `identity::bot_id` — Ed25519 keypair generated via springtale-crypto. Stored in vault.
78+
- Phase 1b: keypair is the bot's identity. Simple.
79+
- Phase 3: HKDF derives per-community pseudonyms from this keypair for Rekindle integration. The derivation code is not written yet — just the keypair.
80+
81+
**Safety features (from audit §2.8 IPV threat model):**
82+
- No OS notifications by default. Connector events and bot responses do not trigger push notifications unless user explicitly enables via `/prefs set notifications on`.
83+
- Vault auto-lock after configurable inactivity (default: 5 min). CLI prompts for passphrase to resume. Tauri modal in Phase 2b.
84+
85+
**Integration with springtaled:**
86+
- `springtaled` startup adds a new step between scheduler start and API start: initialize `springtale-bot` with references to connector registry, rule engine, scheduler, and store
87+
- Bot event loop runs as a tokio task alongside existing scheduler tasks
88+
- The rule engine from Phase 1a runs INSIDE the bot's event loop — cron rules, webhook rules, filesystem rules all fire through the same dispatch path
89+
90+
**Testing (without Telegram):**
91+
- Headless integration test: fire a `Trigger::ConnectorEvent` -> bot routes -> handler executes -> response generated
92+
- Command routing tests: prefix match, pattern match, alias resolution, fallback
93+
- Session isolation tests: two users send commands concurrently, state does not leak
94+
- Memory compaction test: fill to N+1, verify oldest dropped
95+
96+
## Milestone 1b-ii: connector-telegram
97+
98+
Standard connector structure per `.claude/rules/connector-guidelines.md`.
99+
100+
**How to build:**
101+
102+
Telegram Bot API client. Two modes of receiving updates:
103+
1. **Webhook mode:** Telegram sends HTTPS POST to your endpoint. Requires public URL. Uses the management API's webhook endpoint: `POST /webhook/connector-telegram/message_received`.
104+
2. **Long-polling mode:** Bot calls `getUpdates` in a loop. Works behind NAT. Default for dev/self-hosted.
105+
106+
Mode selected in connector config. Long-polling is default.
107+
108+
**Auth:**
109+
- Bot token from BotFather, stored as `Secret<String>` in connector config
110+
- Webhook signature verification not natively supported by Telegram Bot API (unlike GitHub). Instead: use secret path token in webhook URL as auth.
111+
112+
**Connector trait implementation:**
113+
- `triggers()`: `message_received`, `command_received` (filtered by /prefix)
114+
- `actions()`: `send_message`, `send_photo`, `edit_message`, `delete_message`, `send_inline_keyboard`
115+
- `execute("send_message", { chat_id, text, parse_mode })` -> call Telegram `sendMessage` API
116+
- `on_event("message_received", handler)` -> handler called for every incoming message
117+
118+
**Typed API client:**
119+
- `client::api.rs` — reqwest client to `https://api.telegram.org/bot{token}/`
120+
- All request/response types as Rust structs with serde
121+
- Methods: `send_message`, `send_photo`, `edit_message_text`, `delete_message`, `get_updates`, `set_webhook`, `delete_webhook`
122+
- Inline keyboard builder for interactive buttons
123+
- Message formatting: MarkdownV2 and HTML modes
124+
125+
**Research needed:** Telegram Bot API docs (https://core.telegram.org/bots/api).
126+
Update polling semantics (`offset` parameter for confirming received updates).
127+
MarkdownV2 escaping rules (Telegram has unusual escaping requirements).
128+
Rate limits (30 messages/second to same chat, 20 messages/minute to same group).
129+
130+
**Integration:**
131+
- Install: `springtale connector install ./connector-telegram.toml`
132+
- Bot auto-registers Telegram message handler in event loop
133+
- User sends `/search tokyo weather` in Telegram chat
134+
- Bot receives via connector-telegram `on_event`
135+
- Router matches `/search` prefix -> SearchHandler
136+
- Handler calls `connector-presearch.execute("search", { query: "tokyo weather" })`
137+
- Result formatted via response template
138+
- Handler calls `connector-telegram.execute("send_message", { chat_id, text: formatted_result })`
139+
- User sees result in Telegram
140+
141+
**Testing:**
142+
- Mock Telegram API server (axum test server returning canned responses)
143+
- Test: send_message serialization matches Telegram API format
144+
- Test: getUpdates polling loop handles timeout, empty response, error response
145+
- Integration test: message received -> routed -> connector called -> reply sent
146+
147+
## Not In Phase 1b
148+
149+
- No AI fallback parser (Phase 2a — router returns "unknown command" for now)
150+
- No AI-powered memory compaction (Phase 2a — truncation only)
151+
- No HKDF pseudonym derivation on BotId (Phase 3)
152+
- No recursive pipeline orchestration (Phase 2a)
153+
- No sub-agent spawning (Phase 2a)
154+
- No sentinel behavioral monitor (Phase 2a)
155+
- No additional chat connectors beyond Telegram (Phase 2a)
156+
- No ATProto bridge (Phase 2a)
157+
- No Rekindle bridge (Phase 3)

.claude/phases/phase-2a.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Phase 2a — Full Chat Coverage + AI Adapters
2+
3+
> Source: `docs/current-arch/ARCHITECTURE.md` §3, §6.7, §6.9-6.10, §14.3-14.5
4+
> Depends on: Phase 1b complete
5+
6+
## Goal
7+
8+
Full chat platform coverage. AI adapters as optional fallback parser and
9+
pipeline action. NL->Rule parser. Runtime behavioral monitoring. Sub-agent
10+
orchestration. This is where Springtale becomes a full, safe replacement
11+
for the existing agent/bot platforms.
12+
13+
## Transport Upgrade
14+
15+
Phase 2a upgrades from `LocalTransport` (Unix sockets, same-machine) to
16+
`HttpTransport` (LAN/VPN, axum server + reqwest client, mTLS).
17+
18+
**How to integrate:**
19+
- Implement `HttpTransport` in `crates/springtale-transport/src/http/`
20+
- `http::server` — axum inbound endpoint, mTLS with rustls
21+
- `http::client` — reqwest outbound, peer cert validation
22+
- `runtime::boot` in `springtaled` selects transport based on `springtale.toml` config
23+
- All existing code uses `Arc<dyn Transport>` — no changes needed outside transport crate
24+
- mTLS cert fingerprints stored in `springtale-store`
25+
26+
**Research needed:** mTLS setup with rustls + reqwest. Peer certificate
27+
pinning pattern. DNS rebinding protection via Host header validation.
28+
29+
## AI Adapters
30+
31+
The `AiAdapter` trait (defined in Phase 1a with NoopAdapter only) gets
32+
real implementations. These are thin HTTP clients to user-provided endpoints.
33+
34+
**How to integrate:**
35+
- Each adapter is a feature-gated module in `crates/springtale-ai/src/`
36+
- `ollama::adapter` — HTTP client to `localhost:11434`, Ollama REST API
37+
- `openai::adapter``/v1/chat/completions` for any OpenAI-compatible API (GPT, Gemini, Kimi, DeepSeek, OpenRouter)
38+
- `anthropic::adapter``/v1/messages` with native tool_use
39+
- `voice::stt` — Whisper-compatible speech-to-text bridge
40+
- `voice::tts` — ElevenLabs / Piper text-to-speech bridge
41+
- User configures endpoint in `springtale.toml` or `springtale-cli ai configure`
42+
- HTTPS validated (HTTP rejected unless `--allow-insecure-ai-endpoint`)
43+
- `AiOptions { max_tokens, timeout }` controls cost. Default: 4096 tokens, 30s
44+
- Response body 10 MiB limit
45+
- `AiRequest` is a closed enum — `Secret<T>` values cannot serialize into it
46+
47+
**Research needed:** Ollama REST API schema. OpenAI /v1/chat/completions
48+
streaming SSE format. Anthropic /v1/messages tool_use format. Whisper API
49+
variants (OpenAI, local faster-whisper).
50+
51+
## NL->Rule Parser
52+
53+
User says "notify me on Discord when my Kick stream goes live" -> AI generates
54+
structured `Rule` (TOML) -> rule runs deterministically forever, no AI needed again.
55+
56+
**How to integrate:**
57+
- `springtale-ai::parser::rule_gen` — prompt templates inject available connector schemas
58+
- `springtale-ai::parser::prompt` — structured output prompt for the user's AI
59+
- Output is a `Rule` struct (from springtale-core), validated before saving
60+
- User reviews generated TOML before enabling
61+
- Works with any AI adapter (Ollama, OpenAI, Anthropic)
62+
63+
## Chat Connectors (7)
64+
65+
Each follows `.claude/rules/connector-guidelines.md`.
66+
67+
| Connector | Key Integration Notes |
68+
|---|---|
69+
| `connector-discord` | Gateway WebSocket (not REST polling). Slash command registration. Voice channel join for presence. Embed builder. discord.js-equivalent in Rust. |
70+
| `connector-signal` | signal-cli bridge process. E2E encrypted messages. Disappearing messages support. No phone number exposed to Springtale. |
71+
| `connector-whatsapp` | Baileys-equivalent protocol. **Likely NativeConnector** (needs persistent WebSocket + Signal Protocol — too complex for WASM sandbox). QR code pairing. |
72+
| `connector-matrix` | Matrix SDK (ruma crate). Federated rooms. E2E encryption via vodozemac. Room state management. |
73+
| `connector-irc` | Lightweight. Raw TCP + rustls-tls. Channel join/part/msg. SASL auth. |
74+
| `connector-slack` | Socket mode (not HTTP). Slash commands. Block Kit. Thread replies. |
75+
| `connector-nostr` | NIP-01 relay client. Event signing with Ed25519. Encrypted DMs (NIP-04/NIP-44). |
76+
77+
**Research needed per connector:** API docs, auth flows, WebSocket vs REST,
78+
rate limits, message format. connector-whatsapp needs special attention —
79+
Baileys reverse-engineers WhatsApp's protocol and may need NativeConnector
80+
trust level rather than WASM sandbox.
81+
82+
## springtale-sentinel
83+
84+
Runtime behavioral monitor. Ships with hard constraints only (Layer 1).
85+
86+
**How to integrate:**
87+
- New crate: `crates/springtale-sentinel/`
88+
- Sentinel instance created in `springtaled` startup, passed to bot runtime
89+
- Bot runtime wraps pipeline dispatch: `sentinel.evaluate(action, connector)` before each stage
90+
- Sentinel does NOT live in springtale-core (no dependency cycle)
91+
- Integration point is `springtaled` and `springtale-bot` event loop
92+
93+
**Modules:**
94+
- `rate_limiter` — actions/minute per connector, default 60, configurable
95+
- `circuit_breaker` — 3 consecutive failures -> stage disabled, user notified, auto-reset after cooldown
96+
- `dead_man` — N actions/minute without user interaction -> pause all pipelines
97+
- `toxic_pairs` — dangerous capability combos blocked at install time
98+
- `impact` — action tagged read-only / reversible / destructive
99+
- `audit::trail` — append-only SQLite table, every action logged
100+
- `audit::export` — export for CLI review + compliance
101+
102+
## Recursive Pipeline Orchestration
103+
104+
Derived from Clicky's subagent pattern.
105+
106+
**How to integrate:**
107+
- `springtale-bot::orchestrator::recursive` — pipeline stage can spawn child pipeline
108+
- Each child gets `parent_remaining_fuel / num_children` fuel budget
109+
- Max concurrent children: 8. Max recursive depth: 4.
110+
- Children inherit read-only `PipelineContext` snapshot. Write to own context.
111+
- Parent collects child results at spawn-point stage.
112+
- `springtale-bot::orchestrator::subagent` — spawn child agent with scoped capabilities
113+
- `springtale-bot::orchestrator::coordinator` — multi-bot coordination via shared state
114+
115+
## ATProto Bot Bridge
116+
117+
Derived from malwarevangelist-bot.
118+
119+
**How to integrate:**
120+
- `springtale-bot::bridge::atproto` — wraps connector-bluesky triggers/actions
121+
- `ATProtoBotBridge` provides `on_mention`, `on_follow`, `post`, `reply`
122+
- Maps Bluesky events to bot event loop
123+
- Session management patterns from malwarevangelist-bot's enCore engine
124+
125+
## Memory-Only / Ephemeral Mode
126+
127+
`--ephemeral` flag: vault exists only in memory. All state lost on exit.
128+
129+
**How to integrate:**
130+
- `springtale-store::backend::memory` — in-memory StorageBackend implementation
131+
- `springtale-crypto::vault` — option to skip file persistence
132+
- `springtaled --ephemeral` creates everything in memory, warns user on startup
133+
134+
## Not In Phase 2a
135+
136+
- No Tauri desktop/mobile shell (2b)
137+
- No visual rule builder (2b)
138+
- No dashboard web UI (2b)
139+
- No browser automation connector (2b)
140+
- No Veilid transport (Phase 3)
141+
- No Rekindle bot bridge (Phase 3)
142+
- No statistical baseline learning for sentinel (deferred)
143+
- No trajectory analysis for sentinel (deferred)

0 commit comments

Comments
 (0)