Skip to content

Commit 84dff52

Browse files
authored
Merge pull request #22 from devwhodevs/feat/v1.6-onboarding-identity
v1.6.0: Onboarding + Identity
2 parents ede44f2 + 8cc90e0 commit 84dff52

File tree

16 files changed

+1722
-157
lines changed

16 files changed

+1722
-157
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## v1.6.0 — Onboarding + Identity (2026-04-10)
4+
5+
### Added
6+
- **Interactive onboarding** (`engraph init`) — polished CLI with welcome banner, vault scan checkmarks, identity prompts via dialoguer, progress bars, actionable next steps
7+
- **Agent onboarding**`engraph init --detect --json` for vault inspection, `--json` for non-interactive apply. Two-phase detect → apply flow for AI agents.
8+
- **`identity` MCP tool + CLI + HTTP** — returns compact L0/L1 identity block (~170 tokens) for AI session context
9+
- **`setup` MCP tool + HTTP** — first-time setup from inside an MCP session (detect/apply modes)
10+
- **`identity_facts` table** — SQLite storage for L0 (static identity) and L1 (dynamic context) facts
11+
- **L1 auto-extraction** — active projects, key people, current focus, OOO status, blocking items extracted during `engraph index`
12+
- **`engraph identity --refresh`** — re-extract L1 facts without full reindex
13+
- **`[identity]` config section** — name, role, vault_purpose in config.toml
14+
- **`[memory]` config section** — feature flags for identity/timeline/mining
15+
16+
### Changed
17+
- MCP tools: 23 → 25
18+
- HTTP endpoints: 24 → 26
19+
- Dependencies: +dialoguer 0.12, +console 0.16, +regex 1
20+
321
## v1.5.5 — Housekeeping (2026-04-10)
422

523
### Added

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Single binary with 26 modules behind a lib crate:
3232
- `indexer.rs` — orchestrates vault walking (via `ignore` crate for `.gitignore` support), diffing, chunking, embedding, writes to store + sqlite-vec + FTS5, vault graph edge building (wikilinks + people detection), and folder centroid computation. Exposes `index_file`, `remove_file`, `rename_file` as public per-file functions. `run_index_shared` accepts external store/embedder for watcher FullRescan. Dimension migration on model change.
3333
- `temporal.rs` — temporal search lane. Extracts note dates from frontmatter `date:` field or `YYYY-MM-DD` filename patterns. Heuristic date parsing for natural language ("today", "yesterday", "last week", "this month", "recent", month names, ISO dates, date ranges). Smooth decay scoring for files near but outside target date range. Provides `extract_note_date()` for indexing and `score_temporal()` + `parse_date_range_heuristic()` for search
3434
- `search.rs` — hybrid search orchestrator. `search_with_intelligence()` runs the full pipeline: orchestrate (intent + expansions) → 5-lane RRF retrieval (semantic + FTS5 + graph + reranker + temporal) per expansion → two-pass RRF fusion. `search_internal()` is a thin wrapper without intelligence models. Adaptive lane weights per query intent including temporal (1.5 weight for time-aware queries). Results display normalized confidence percentages (0-100%) instead of raw RRF scores.
35+
- `identity.rs` — L1 extraction engine: active projects, key people, current focus, OOO, blocking. `format_identity_block()` for compact session context. `extract_l1_facts()` called after indexing.
36+
- `onboarding.rs` — Interactive CLI UX: welcome banner, vault scan, identity prompts (dialoguer), agent mode (--detect --json, --json). `run_interactive()`, `run_detect_json()`, `run_apply_json()`.
3537

3638
`main.rs` is a thin clap CLI (async via `#[tokio::main]`). Subcommands: `index` (with progress bar), `search` (with `--explain`, loads intelligence models when enabled), `status` (shows intelligence state + date coverage stats), `clear`, `init` (intelligence onboarding prompt, detects Obsidian CLI + AI agents), `configure` (`--enable-intelligence`, `--disable-intelligence`, `--model`, `--obsidian-cli`, `--no-obsidian-cli`, `--agent`, `--add-api-key`, `--list-api-keys`, `--revoke-api-key`, `--setup-chatgpt`), `models`, `graph` (show/stats), `context` (read/list/vault-map/who/project/topic), `write` (create/append/update-metadata/move/edit/rewrite/edit-frontmatter/delete), `migrate` (para with `--preview`/`--apply`/`--undo` for PARA vault restructuring), `serve` (MCP stdio server with file watcher + intelligence + optional `--http`/`--port`/`--host`/`--no-auth` for HTTP REST API).
3739

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "engraph"
3-
version = "1.5.5"
3+
version = "1.6.0"
44
edition = "2024"
55
description = "Local knowledge graph for AI agents. Hybrid search + MCP server for Obsidian vaults."
66
license = "MIT"
@@ -25,6 +25,9 @@ tokenizers = { version = "0.22", default-features = false, features = ["fancy-re
2525
sha2 = "0.10"
2626
ureq = "2.12"
2727
indicatif = "0.17"
28+
dialoguer = "0.12"
29+
console = "0.16"
30+
regex = "1"
2831
sqlite-vec = "0.1.8-alpha.1"
2932
zerocopy = { version = "0.7", features = ["derive"] }
3033
rayon = "1"

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ engraph turns your markdown vault into a searchable knowledge graph that any AI
2121
Plain vector search treats your notes as isolated documents. But knowledge isn't flat — your notes link to each other, share tags, reference the same people and projects. engraph understands these connections.
2222

2323
- **5-lane hybrid search** — semantic embeddings + BM25 full-text + graph expansion + cross-encoder reranking + temporal scoring, fused via [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf). An LLM orchestrator classifies queries and adapts lane weights per intent. Time-aware queries like "what happened last week" or "March 2026 notes" activate the temporal lane automatically.
24-
- **MCP server for AI agents**`engraph serve` exposes 22 tools (search, read, section-level editing, frontmatter mutations, vault health, context bundles, note creation, PARA migration) that Claude, Cursor, or any MCP client can call directly.
25-
- **HTTP REST API**`engraph serve --http` adds an axum-based HTTP server alongside MCP with 23 REST endpoints, API key authentication, rate limiting, and CORS. Web-based agents and scripts can query your vault with simple `curl` calls.
24+
- **MCP server for AI agents**`engraph serve` exposes 25 tools (search, read, section-level editing, frontmatter mutations, vault health, context bundles, note creation, PARA migration, identity) that Claude, Cursor, or any MCP client can call directly.
25+
- **HTTP REST API**`engraph serve --http` adds an axum-based HTTP server alongside MCP with 26 REST endpoints, API key authentication, rate limiting, and CORS. Web-based agents and scripts can query your vault with simple `curl` calls.
2626
- **Section-level editing** — AI agents can read, replace, prepend, or append to specific sections by heading. Full note rewriting with frontmatter preservation. Granular frontmatter mutations (set/remove fields, add/remove tags and aliases).
2727
- **Vault health diagnostics** — detect orphan notes, broken wikilinks, stale content, and tag hygiene issues. Available as MCP tool and CLI command.
2828
- **Obsidian CLI integration** — auto-detects running Obsidian and delegates compatible operations. Circuit breaker (Closed/Degraded/Open) ensures graceful fallback.
@@ -61,7 +61,7 @@ Your vault (markdown files)
6161
│ Search: Orchestrator → 4-lane retrieval │
6262
│ → Reranker → Two-pass RRF fusion │
6363
│ │
64-
22 MCP tools + 23 REST endpoints │
64+
25 MCP tools + 26 REST endpoints │
6565
└─────────────────────────────────────────────┘
6666
6767
@@ -268,7 +268,7 @@ Returns orphan notes (no links in or out), broken wikilinks, stale notes, and ta
268268

269269
`engraph serve --http` adds a full REST API alongside the MCP server, exposing the same capabilities over HTTP for web agents, scripts, and integrations.
270270

271-
**24 endpoints:**
271+
**26 endpoints:**
272272

273273
| Method | Endpoint | Permission | Description |
274274
|--------|----------|------------|-------------|
@@ -292,6 +292,8 @@ Returns orphan notes (no links in or out), broken wikilinks, stale notes, and ta
292292
| POST | `/api/unarchive` | write | Restore archived note |
293293
| POST | `/api/update-metadata` | write | Update note metadata |
294294
| POST | `/api/delete` | write | Delete note (soft or hard) |
295+
| GET | `/api/identity` | read | User identity (L0) and current context (L1) |
296+
| POST | `/api/setup` | write | First-time onboarding setup (detect/apply modes) |
295297
| POST | `/api/reindex-file` | write | Re-index a single file after external edits |
296298
| POST | `/api/migrate/preview` | write | Preview PARA migration (classify + suggest moves) |
297299
| POST | `/api/migrate/apply` | write | Apply PARA migration (move files) |
@@ -526,7 +528,7 @@ STYLE:
526528
| Search method | 5-lane RRF (semantic + BM25 + graph + reranker + temporal) | Vector similarity only | Keyword only |
527529
| Query understanding | LLM orchestrator classifies intent, adapts weights | None | None |
528530
| Understands note links | Yes (wikilink graph traversal) | No | Limited (backlinks panel) |
529-
| AI agent access | MCP server (22 tools) + HTTP REST API (23 endpoints) | Custom API needed | No |
531+
| AI agent access | MCP server (25 tools) + HTTP REST API (26 endpoints) | Custom API needed | No |
530532
| Write capability | Create/edit/rewrite/delete with smart filing | No | Manual |
531533
| Vault health | Orphans, broken links, stale notes, tag hygiene | No | Limited |
532534
| Real-time sync | File watcher, 2s debounce | Manual re-index | N/A |
@@ -543,8 +545,9 @@ engraph is not a replacement for Obsidian — it's the intelligence layer that s
543545
- LLM research orchestrator: query intent classification + query expansion + adaptive lane weights
544546
- llama.cpp inference via Rust bindings (GGUF models, Metal GPU on macOS, CUDA on Linux)
545547
- Intelligence opt-in: heuristic fallback when disabled, LLM-powered when enabled
546-
- MCP server with 23 tools (8 read, 10 write, 1 index, 1 diagnostic, 3 migrate) via stdio
547-
- HTTP REST API with 24 endpoints, API key auth (`eg_` prefix), rate limiting, CORS — enabled via `engraph serve --http`
548+
- MCP server with 25 tools (8 read, 10 write, 2 identity, 1 index, 1 diagnostic, 3 migrate) via stdio
549+
- HTTP REST API with 26 endpoints, API key auth (`eg_` prefix), rate limiting, CORS — enabled via `engraph serve --http`
550+
- User identity with L0/L1 tiered context for AI agent session starts
548551
- Section-level reading and editing: target specific headings with replace/prepend/append modes
549552
- Full note rewriting with automatic frontmatter preservation
550553
- Granular frontmatter mutations: set/remove fields, add/remove tags and aliases
@@ -573,7 +576,7 @@ engraph is not a replacement for Obsidian — it's the intelligence layer that s
573576
- [x] ~~HTTP/REST API — complement MCP with a standard web API~~ (v1.3)
574577
- [x] ~~PARA migration — AI-assisted vault restructuring with preview/apply/undo~~ (v1.4)
575578
- [x] ~~ChatGPT Actions — OpenAPI 3.1.0 spec + plugin manifest + `--setup-chatgpt` helper~~ (v1.5)
576-
- [ ] Identity — user context at session start, enhanced onboarding (v1.6)
579+
- [x] ~~Identity — user context at session start, enhanced onboarding~~ (v1.6)
577580
- [ ] Timeline — temporal knowledge graph with point-in-time queries (v1.7)
578581
- [ ] Mining — automatic fact extraction from vault notes (v1.8)
579582

src/config.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,38 @@ pub struct PluginConfig {
4343
pub public_url: Option<String>,
4444
}
4545

46+
/// User identity for AI agent context.
47+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
48+
#[serde(default)]
49+
pub struct IdentityConfig {
50+
pub name: Option<String>,
51+
pub role: Option<String>,
52+
pub vault_purpose: Option<String>,
53+
}
54+
55+
/// Memory layer feature flags.
56+
#[derive(Debug, Clone, Serialize, Deserialize)]
57+
#[serde(default)]
58+
pub struct MemoryConfig {
59+
pub identity_enabled: bool,
60+
pub timeline_enabled: bool,
61+
pub mining_enabled: bool,
62+
pub mining_strategy: String,
63+
pub mining_on_index: bool,
64+
}
65+
66+
impl Default for MemoryConfig {
67+
fn default() -> Self {
68+
Self {
69+
identity_enabled: true,
70+
timeline_enabled: true,
71+
mining_enabled: true,
72+
mining_strategy: "auto".into(),
73+
mining_on_index: true,
74+
}
75+
}
76+
}
77+
4678
/// HTTP REST API configuration.
4779
#[derive(Debug, Clone, Serialize, Deserialize)]
4880
#[serde(default)]
@@ -104,6 +136,10 @@ pub struct Config {
104136
/// HTTP REST API settings.
105137
#[serde(default)]
106138
pub http: HttpConfig,
139+
#[serde(default)]
140+
pub identity: IdentityConfig,
141+
#[serde(default)]
142+
pub memory: MemoryConfig,
107143
}
108144

109145
impl Default for Config {
@@ -118,6 +154,8 @@ impl Default for Config {
118154
obsidian: ObsidianConfig::default(),
119155
agents: AgentsConfig::default(),
120156
http: HttpConfig::default(),
157+
identity: IdentityConfig::default(),
158+
memory: MemoryConfig::default(),
121159
}
122160
}
123161
}
@@ -379,4 +417,33 @@ public_url = "https://vault.example.com"
379417
let config: Config = toml::from_str(toml).unwrap();
380418
assert_eq!(config.http.plugin.name.as_deref(), Some("my-vault"));
381419
}
420+
421+
#[test]
422+
fn test_identity_config_deserializes() {
423+
let toml_str = r#"
424+
[identity]
425+
name = "Test User"
426+
role = "Developer"
427+
vault_purpose = "notes"
428+
"#;
429+
let config: Config = toml::from_str(toml_str).unwrap();
430+
assert_eq!(config.identity.name, Some("Test User".into()));
431+
assert_eq!(config.identity.role, Some("Developer".into()));
432+
assert_eq!(config.identity.vault_purpose, Some("notes".into()));
433+
}
434+
435+
#[test]
436+
fn test_identity_config_defaults_to_empty() {
437+
let config = Config::default();
438+
assert!(config.identity.name.is_none());
439+
assert!(config.identity.role.is_none());
440+
}
441+
442+
#[test]
443+
fn test_memory_config_defaults() {
444+
let config = Config::default();
445+
assert!(config.memory.identity_enabled);
446+
assert!(config.memory.timeline_enabled);
447+
assert!(config.memory.mining_enabled);
448+
}
382449
}

src/http.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,14 @@ struct ReindexFileBody {
332332
file: String,
333333
}
334334

335+
#[derive(Debug, Deserialize)]
336+
struct SetupBody {
337+
mode: String,
338+
name: Option<String>,
339+
role: Option<String>,
340+
purpose: Option<String>,
341+
}
342+
335343
// ---------------------------------------------------------------------------
336344
// CORS
337345
// ---------------------------------------------------------------------------
@@ -388,6 +396,9 @@ pub fn build_router(state: ApiState) -> Router {
388396
.route("/api/delete", post(handle_delete))
389397
// Index maintenance
390398
.route("/api/reindex-file", post(handle_reindex_file))
399+
// Identity endpoints
400+
.route("/api/identity", get(handle_identity))
401+
.route("/api/setup", post(handle_setup))
391402
// Migration endpoints
392403
.route("/api/migrate/preview", post(handle_migrate_preview))
393404
.route("/api/migrate/apply", post(handle_migrate_apply))
@@ -1066,6 +1077,56 @@ async fn handle_reindex_file(
10661077
})))
10671078
}
10681079

1080+
// ---------------------------------------------------------------------------
1081+
// Identity / setup endpoint handlers
1082+
// ---------------------------------------------------------------------------
1083+
1084+
async fn handle_identity(
1085+
State(state): State<ApiState>,
1086+
headers: HeaderMap,
1087+
) -> Result<impl IntoResponse, ApiError> {
1088+
authorize(&headers, &state, false)?;
1089+
let store = state.store.lock().await;
1090+
let config = crate::config::Config::load().unwrap_or_default();
1091+
let block = crate::identity::format_identity_block(&config, &store)
1092+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1093+
Ok(Json(serde_json::json!({ "identity": block })))
1094+
}
1095+
1096+
async fn handle_setup(
1097+
State(state): State<ApiState>,
1098+
headers: HeaderMap,
1099+
Json(body): Json<SetupBody>,
1100+
) -> Result<impl IntoResponse, ApiError> {
1101+
authorize(&headers, &state, true)?;
1102+
match body.mode.as_str() {
1103+
"detect" => {
1104+
let result = crate::onboarding::run_detect_json(&state.vault_path)
1105+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1106+
Ok(Json(result))
1107+
}
1108+
"apply" => {
1109+
let mut config = crate::config::Config::load().unwrap_or_default();
1110+
let data_dir = crate::config::Config::data_dir()
1111+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1112+
let flags = crate::onboarding::ApplyFlags {
1113+
name: body.name,
1114+
role: body.role,
1115+
purpose: body.purpose,
1116+
identity_only: false,
1117+
reindex_only: false,
1118+
};
1119+
let result =
1120+
crate::onboarding::run_apply_json(&state.vault_path, &mut config, &data_dir, flags)
1121+
.map_err(|e| ApiError::internal(&format!("{e:#}")))?;
1122+
Ok(Json(result))
1123+
}
1124+
other => Err(ApiError::bad_request(&format!(
1125+
"Unknown mode: {other}. Use 'detect' or 'apply'."
1126+
))),
1127+
}
1128+
}
1129+
10691130
// ---------------------------------------------------------------------------
10701131
// Tests
10711132
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)