Skip to content

Commit 0838219

Browse files
authored
feat(core): add persistent Node.js client (#112) (#114)
* feat(core): add persistent Node.js client * docs: document persistent Node.js integrations * fix(landing): align npm version accessible label
1 parent c7a2fcd commit 0838219

35 files changed

Lines changed: 982 additions & 205 deletions

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
- `packages/ragmir-tts` is the optional local/offline audio add-on used by `rgr audio`.
1616
- `packages/ragmir-landing` is a self-contained, telemetry-free Astro site. Keep it static, open-source focused, and free of vendor deployment configuration.
1717
- Ragmir Core stays retrieval-first: `local-hash` supports offline retrieval, `transformers` is the explicit semantic option, and local chat remains a separate add-on.
18+
- Long-running Node.js processes use one `RagmirClient` per project root and close it during shutdown. Keep the top-level API for one-shot scripts.
19+
- Ragmir does not provide an HTTP server or fixed port. A network-facing host owns transport security, authentication, authorization, and rate limits.
20+
- Ingestion is serialized per local index inside one Node.js process; do not claim a distributed writer lock.
1821
- Public copy must lead with model-agnostic Core and the choice between the user's preferred AI or automation and a fully local consumer. Qwen and Gemma are optional Chat profiles, never Core or MCP requirements.
1922

2023
## Privacy and ingestion

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ native desktop shell, or cloud-vendor deployment configuration. Keep OCR optiona
3232
local, remote model downloads explicit, and normal confidential retrieval offline.
3333
Describe Core as model-agnostic: users can connect their preferred AI or automation, or keep the
3434
consumer local. Qwen and Gemma are optional Chat profiles, never Core or MCP requirements.
35+
For repeated retrieval in a stateful Node.js process, use one `RagmirClient` per project root and
36+
close it during shutdown. Ragmir does not provide an HTTP server or fixed port; network-facing hosts
37+
own transport security, authentication, authorization, and rate limits.
3538

3639
<!-- gitnexus:start -->
3740
# GitNexus — Code Intelligence

README.md

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,14 @@ owns the source files.
6060
| Interface | Use it for | Result |
6161
| --- | --- | --- |
6262
| `rgr` CLI | Setup, ingest, search, audit, and maintenance | Human-readable or JSON output |
63-
| TypeScript API | Embed retrieval in a Node.js application | Typed results with citations |
63+
| TypeScript API | Embed retrieval in a script or stateful Node.js worker | Typed results with citations and explicit lifecycle |
6464
| Local MCP server | Give your preferred agent bounded project context | Read-focused retrieval tools |
6565
| Ragmir Chat | Keep answer generation on the workstation | Cited offline synthesis |
6666
| Ragmir TTS | Turn a text brief into audio | Local WAV or explicit online MP3 |
6767

6868
Use the CLI or MCP for interactive agent work. Use the TypeScript API when a repeatable Node.js
69-
process owns the control flow.
69+
process owns the control flow. Ragmir does not open an HTTP port: applications own their network,
70+
authentication, and authorization boundary.
7071

7172
Ragmir Core stays retrieval-first. `ask()` returns cited context without calling an LLM. Local chat
7273
and audio are separate capabilities, so retrieval remains useful on machines that should not run a
@@ -180,23 +181,37 @@ binary support.
180181
## TypeScript API
181182

182183
```ts
183-
import { ingest, search } from "@jcode.labs/ragmir"
184-
185-
await ingest({ cwd: process.cwd() })
186-
187-
const results = await search("Which decision changed the rollout?", {
188-
topK: 5,
189-
explain: true,
190-
})
191-
192-
for (const result of results) {
193-
console.log(result.citation, result.text)
184+
import { createRagmirClient, isRagmirError } from "@jcode.labs/ragmir"
185+
186+
const ragmir = await createRagmirClient({ cwd: process.cwd() })
187+
try {
188+
await ragmir.ingest({ timeoutMs: 120_000 })
189+
190+
const results = await ragmir.search("Which decision changed the rollout?", {
191+
topK: 5,
192+
explain: true,
193+
timeoutMs: 10_000,
194+
})
195+
196+
for (const result of results) {
197+
console.log(result.citation, result.text)
198+
}
199+
} catch (error) {
200+
if (isRagmirError(error)) console.error(error.code, error.retryable)
201+
else throw error
202+
} finally {
203+
await ragmir.close()
194204
}
195205
```
196206

197-
Core also exports `previewChunks`, `ask`, `research`, `audit`, `doctor`, `securityAudit`,
198-
`discoverKnowledgeBases`, bounded context helpers, `serveMcp`, and setup helpers. See the
199-
[API reference](./docs/api-reference.md) for the public surface.
207+
Reuse one client per project root in a long-running process. It keeps one local LanceDB connection,
208+
serializes ingestion for the same index inside that process, accepts `AbortSignal` and `timeoutMs`,
209+
and waits for active operations during `close()`. One-shot `ingest`, `search`, `ask`, and `research`
210+
functions remain available for short scripts.
211+
212+
Core also exports `previewChunks`, `audit`, `doctor`, `securityAudit`, bounded context helpers,
213+
closeable MCP construction helpers, and setup helpers. See the [API reference](./docs/api-reference.md)
214+
for the complete public surface.
200215

201216
## Privacy boundaries
202217

context7.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"rules": [
2121
"Ragmir Core returns cited retrieval context only; it does not synthesize answers itself.",
2222
"Any compatible coding agent, script, CLI, TypeScript application, or MCP client can consume Core results; use a local consumer when no passage may leave the machine.",
23+
"Use one `createRagmirClient()` per project root for repeated work in a stateful Node.js process; close it during shutdown and use the top-level functions for one-shot scripts.",
24+
"Ragmir does not open an HTTP port; a network-facing host owns its transport, authentication, authorization, and rate limits.",
2325
"`rgr chat` is optional add-on generation via @jcode.labs/ragmir-chat; the core remains retrieval-only.",
2426
"Qwen and Gemma are optional Ragmir Chat profiles, never requirements of Core, the CLI, the TypeScript API, or MCP.",
2527
"The `local-hash` embedding provider (default) is a lexical sha256 embedding, not semantic; use `transformers` for semantic retrieval.",

docs/agent-integration.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ The generated helpers cover Claude Code, Codex, Kimi, OpenCode, and Cline. Other
8888
the same evidence through the CLI, TypeScript API, or any compatible MCP client. Hermes, n8n
8989
workers, CI jobs, and internal applications do not require a dedicated Ragmir model integration.
9090

91+
Embedding applications can call `createMcpServer(cwd)` to register a caller-owned transport, or
92+
`connectMcpServer(transport, cwd)` to connect it and receive a closeable server handle. The standard
93+
`serveMcp(cwd)` helper remains the simplest local stdio entry point. MCP cancellation propagates to
94+
search, ask, research, and citation expansion. Ragmir does not open an HTTP port; applications that
95+
expose a network transport own its authentication and authorization boundary.
96+
9197
## Verify
9298

9399
```bash

docs/api-reference.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,51 @@ for (const result of results) {
3535
Search results include `relativePath`, `citation`, `chunkIndex`, exact text, line ranges, page ranges
3636
when available, structural context, and optional score explanations.
3737

38+
### Persistent client for Node.js workers
39+
40+
Use one client per project root when a stateful Node.js process performs repeated retrieval. The
41+
client reuses one strongly consistent LanceDB connection and resolves its project root at creation.
42+
43+
```ts
44+
import { createRagmirClient, isRagmirError } from "@jcode.labs/ragmir"
45+
46+
const controller = new AbortController()
47+
const ragmir = await createRagmirClient({ cwd: process.cwd() })
48+
49+
try {
50+
await ragmir.ingest({ signal: controller.signal, timeoutMs: 120_000 })
51+
const results = await ragmir.search("release approval", {
52+
topK: 5,
53+
signal: controller.signal,
54+
timeoutMs: 10_000,
55+
})
56+
console.log(results.map(({ citation }) => citation))
57+
} catch (error) {
58+
if (isRagmirError(error)) {
59+
console.error(error.code, error.retryable)
60+
} else {
61+
throw error
62+
}
63+
} finally {
64+
await ragmir.close()
65+
}
66+
```
67+
68+
`RagmirClient` exposes `ingest`, `search`, `ask`, `research`, `expandCitation`, `status`, `sources`,
69+
and an idempotent `close`. `close()` rejects new work, waits for active operations, then closes the
70+
shared connection. Ingestion targeting the same storage directory is serialized inside one Node.js
71+
process. Cancellation is cooperative between parsing, embedding, storage, and retrieval phases.
72+
73+
`RagmirError.code` is one of `ABORTED`, `CLIENT_CLOSED`, `INTERNAL`, `INVALID_ARGUMENT`, or
74+
`TIMEOUT`. `retryable` is true for cancellation and timeout errors.
75+
76+
The writer queue coordinates clients inside one Node.js process. If several OS processes can ingest
77+
the same storage directory, the host must elect one writer or serialize those ingestion jobs.
78+
79+
This API targets stateful Node.js processes with a local filesystem. It is not an edge or stateless
80+
serverless API, and Ragmir does not provide an HTTP listener. A network-facing application owns
81+
authentication, authorization, rate limits, and transport security.
82+
3883
### Project and source setup
3984

4085
| Export | Purpose |
@@ -65,9 +110,11 @@ when available, structural context, and optional score explanations.
65110
| `evaluateGoldenQueries(options)` | Score retrieval against a local golden-query file. |
66111

67112
`SearchOptions` accepts `cwd`, `topK`, `contextRadius`, `includePaths`, `excludePaths`,
68-
`contextPaths`, and `explain`. When explanation is enabled, each result includes reciprocal-rank
69-
fusion contributions, one-based vector and lexical ranks, vector distance, lexical backend score,
70-
and matched query terms. `ExpandCitationOptions.contextRadius` is clamped to three chunks.
113+
`contextPaths`, `explain`, `signal`, and `timeoutMs`. `IngestOptions`, `ResearchOptions`, and
114+
`ExpandCitationOptions` also accept `signal` and `timeoutMs`. When explanation is enabled, each
115+
result includes reciprocal-rank fusion contributions, one-based vector and lexical ranks, vector
116+
distance, lexical backend score, and matched query terms. `ExpandCitationOptions.contextRadius` is
117+
clamped to three chunks.
71118

72119
Structural context comes from Markdown headings or structured-data paths. It can improve candidate
73120
selection without changing the exact text, offsets, or citations returned to the caller.
@@ -106,6 +153,8 @@ model download must be explicitly enabled before local inference can use it.
106153

107154
| Export | Purpose |
108155
| --- | --- |
156+
| `createMcpServer(cwd?)` | Construct the read-focused MCP server without selecting a transport. |
157+
| `connectMcpServer(transport, cwd?)` | Connect a caller-owned MCP transport and return a closeable server handle. |
109158
| `serveMcp(cwd?)` | Start the local stdio MCP server. |
110159
| `installAgentSkills(options?)` | Install the canonical skill kit for selected native agents. |
111160
| `installSkill(options?)` | Install one bundled skill with ownership checks. |
@@ -121,7 +170,7 @@ model download must be explicitly enabled before local inference can use it.
121170
New integrations should use `rgrCommand` and the `rgr` CLI name. MCP retrieval tools accept a
122171
`maxBytes` value below the configured `mcpMaxOutputBytes` ceiling. Search, ask, and research also
123172
accept compact output. Metrics are returned under `_meta["ragmir/output"]` and summarized by the
124-
metadata-only usage report.
173+
metadata-only usage report. MCP cancellation signals propagate into Core retrieval operations.
125174

126175
### Core type exports
127176

@@ -136,7 +185,7 @@ types that callers commonly compose explicitly.
136185
| Retrieval | `SearchOptions`, `SearchResult`, `SearchContextChunk`, `SearchScoreExplanation`, `AskResult`, `CompactSearchResult`, `ExpandCitationOptions`, `ExpandedCitation` |
137186
| Research and evaluation | `ResearchOptions`, `ResearchReport`, `ResearchEvidence`, `CodeEvidence`, `SourceDiagnostics`, `SourceDuplicateCandidate`, `SourcePathCandidate`, `EvaluationOptions`, `EvaluationResult`, `EvaluationCaseResult`, `GoldenQuery` |
138187
| Bases and sources | `KnowledgeBaseIdentity`, `KnowledgeBaseInfo`, `KnowledgeBaseInventory`, `KnowledgeBaseContextReport`, `KnowledgeBaseSourceCatalog`, `AddSourceEntriesOptions`, `AddSourceEntriesResult`, `SourceEntriesResult` |
139-
| Operations | `DoctorReport`, `SecurityAuditReport`, `DestroyIndexResult`, `AccessLogAction`, `AccessLogUsageOptions`, `AccessLogUsageReport`, `McpOutputTool`, `McpOutputUsageReport`, `RedactionCount` |
188+
| Operations | `RagmirClientOptions`, `OperationOptions`, `RagmirErrorCode`, `DoctorReport`, `SecurityAuditReport`, `DestroyIndexResult`, `AccessLogAction`, `AccessLogUsageOptions`, `AccessLogUsageReport`, `McpOutputTool`, `McpOutputUsageReport`, `RedactionCount` |
140189
| Embeddings and OCR | `EnableSemanticEmbeddingsResult`, `PullEmbeddingModelResult`, `ConfigurePdfOcrOptions`, `ConfigurePdfOcrResult`, `ExtractPdfPageOptions`, `OcrExecutableStatus`, `PdfOcrEngine`, `PdfOcrEngineSelection`, `PdfOcrStatus` |
141190
| Agent integration | `AgentHelperFile`, `AgentInstallMode`, `AgentInstallScope`, `AgentIntegrationReport`, `AgentSkillInstallation`, `AgentTarget`, `InstallAgentSkillsOptions`, `InstallAgentSkillsResult`, `InstallSkillOptions`, `InstallSkillResult`, `RagmirRunnerMode` |
142191
| Setup and commands | `SetupOptions`, `SetupResult`, `SetupSemanticResult`, `PackageManager`, `RagmirCommand`, `PromptRouteDecision`, `PromptRouteTool` |

docs/configuration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,8 @@ RAGMIR_MCP_MAX_OUTPUT_BYTES=16384 rgr mcp
7070

7171
Environment overrides cover selected runtime settings such as models, retrieval limits, access logs,
7272
and extractor commands. Run `rgr status --json` to inspect the effective result.
73+
74+
For a long-running process that hosts more than one isolated project workflow, create one
75+
`RagmirClient` per project root and keep process-wide environment overrides stable after startup.
76+
Close every client during shutdown. If several OS processes can ingest the same storage directory,
77+
the host must coordinate a single writer.

llms.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ store.
1515
agent or optional local chat add-on.
1616
- Consumer choice: use the AI or automation you already have through CLI, TypeScript, or MCP. Use a
1717
local consumer or optional Chat when no retrieved passage may leave the machine.
18+
- Persistent API: `createRagmirClient()` reuses one local connection in a stateful Node.js worker,
19+
supports cancellation and timeouts, and closes gracefully after active operations.
20+
- Service boundary: Ragmir does not open an HTTP port. Network transports, authentication, and
21+
authorization belong to the embedding application.
1822
- Default retrieval: local-hash, with no model download. Semantic embeddings and local GGUF chat are
1923
explicit opt-ins.
2024
- Optional packages: `@jcode.labs/ragmir-chat` for local cited GGUF synthesis and

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
"sovereign-rag",
2222
"mcp",
2323
"ai-agents",
24+
"coding-agents",
25+
"nodejs",
26+
"typescript",
2427
"private-ai",
2528
"local-first",
2629
"knowledge-base"

packages/ragmir-core/README.md

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ Use that command from a local shell script, a Node.js worker, or a CI step after
7373
and its local `.ragmir/` state. The process returns machine-readable cited passages; the workflow
7474
decides whether to continue, request human approval, or stop.
7575

76-
For long-running integrations, start the local stdio server with `npx rgr serve-mcp`. The MCP surface
77-
is bounded and read-focused: status, source coverage, search, retrieval-only ask, research, exact
78-
citation expansion, audit, evaluation, usage, and security checks. It never exposes index deletion.
76+
For long-running agent integrations, start the local stdio server with `npx rgr serve-mcp`. For a
77+
Node.js worker that owns the control flow, use `createRagmirClient()` and reuse one client per project
78+
root. Ragmir does not open an HTTP port or define an authentication layer.
7979

8080
## Search directly from the CLI
8181

@@ -113,32 +113,42 @@ multi-query retrieval pass and reports missing or weak evidence.
113113
## TypeScript API
114114

115115
```ts
116-
import { ingest, search } from "@jcode.labs/ragmir"
117-
118-
await ingest({ cwd: process.cwd() })
119-
120-
const results = await search("Which decision changed the rollout?", {
121-
cwd: process.cwd(),
122-
topK: 5,
123-
})
124-
125-
for (const result of results) {
126-
console.log(result.citation, result.text)
116+
import { createRagmirClient } from "@jcode.labs/ragmir"
117+
118+
const ragmir = await createRagmirClient({ cwd: process.cwd() })
119+
try {
120+
await ragmir.ingest({ timeoutMs: 120_000 })
121+
122+
const results = await ragmir.search("Which decision changed the rollout?", {
123+
topK: 5,
124+
timeoutMs: 10_000,
125+
})
126+
127+
for (const result of results) {
128+
console.log(result.citation, result.text)
129+
}
130+
} finally {
131+
await ragmir.close()
127132
}
128133
```
129134

135+
The persistent client reuses one local database connection, supports cooperative cancellation, and
136+
waits for active work before shutdown. The top-level functions remain the smallest API for one-shot
137+
scripts.
138+
130139
Frequently used exports:
131140

132141
| Export | Purpose |
133142
| --- | --- |
134143
| `setupProject`, `addSourceEntries` | Initialize project state and select files |
135144
| `discoverKnowledgeBases`, `knowledgeBaseIdentity` | Route root and nested monorepo bases |
136145
| `getKnowledgeBaseContext`, `getKnowledgeBaseSourceCatalog` | Give agents bounded readiness and source context |
146+
| `createRagmirClient`, `RagmirClient` | Reuse local retrieval safely in a stateful Node.js process |
137147
| `ingest`, `audit` | Build the index and compare it with files on disk |
138148
| `previewChunks` | Inspect redacted chunks and distributions without writing storage |
139149
| `search`, `ask`, `research`, `expandCitation` | Retrieve or expand cited passages |
140150
| `doctor`, `securityAudit` | Inspect readiness and local privacy posture |
141-
| `serveMcp` | Start the read-focused local MCP server |
151+
| `createMcpServer`, `connectMcpServer`, `serveMcp` | Construct, connect, or start the read-focused local MCP server |
142152
| `configurePdfOcr`, `inspectPdfOcr` | Configure and inspect local PDF OCR |
143153

144154
See the [complete API reference](https://github.com/jcode-works/jcode-ragmir/blob/main/docs/api-reference.md)

0 commit comments

Comments
 (0)