@asobacloud/nehanda 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Nehanda Command-Line Interface (`nehanda-cli`)
|
|
2
2
|
|
|
3
|
-
An agentic terminal REPL and single-process engine built for
|
|
3
|
+
An agentic terminal REPL and single-process engine built for energy systems research and software development. `nehanda-cli` connects directly to our flagship [Nehanda v3](https://huggingface.co/asoba/nehanda-v3-27b), normalizes multi-OEM energy telemetry through the ODSE protocol, and enforces a governed development workflow across the full research-to-production lifecycle. It also connects to local or cloud-based Ollama models, LM Studio instances, or any OpenAI-compatible API.
|
|
4
4
|
|
|
5
5
|
Every conversation turn, tool call, phase transition, and permission check is stored in a local, queryable SQLite database you own, ensuring complete transcripts exist for auditing and debugging.
|
|
6
6
|
|
|
@@ -83,12 +83,18 @@ To connect to an Ollama instance running on your network:
|
|
|
83
83
|
|
|
84
84
|
* **In-Process Engine:** Executes turns directly inside the process via `runUserTurn`, eliminating separate server daemons or background HTTP relays.
|
|
85
85
|
|
|
86
|
-
* **
|
|
86
|
+
* **ODSE Energy Normalization:** `lib/energy-middleware.mjs` intercepts MCP tool results from energy data servers and automatically normalizes them to the ODS-E protocol before the model sees the payload. OEM brand is resolved from the MCP server name (e.g. `energy-huawei` → `huawei`) or inferred by field-signature fingerprinting across 20+ OEM schemas (Huawei FusionSolar, Enphase, SolarEdge, Fronius, Sungrow, SMA, Eskom, and others). Multi-channel SCADA arrays and inverter state records render as box-drawing terminal tables with column headers and row-count summaries rather than raw JSON dumps.
|
|
87
|
+
|
|
88
|
+
* **Jev-Mem Memory Architecture:** Every turn, a System-1 control plane ([Laya](https://huggingface.co/convaiinnovations/laya)) classifies observations into a multi-relational memory graph (semantic / temporal / causal / entity edges), then adaptively retrieves evidence before each model call. System-2 receives a compact canonical header + retrieved evidence + last 2 turns, not a raw transcript — cutting prefill 70–85% on long sessions. Domain invariants (ODSE schema mappings, asset capacity limits, simulation bounds) are pinned in the `pinned_context` table and reattached verbatim before every System-2 prompt, preventing model drift and hallucinated parameters across arbitrarily long research sessions. See [Jev-Mem Architecture](#jev-mem-architecture) below.
|
|
87
89
|
|
|
88
90
|
* **Dynamic Tool Rescue (`[TOOL_CALL]`):** Native support for endpoints that strip OpenAI tool schemas (such as `nehandaMlProxy`). The engine dynamically injects active tool schemas directly into system prompts as `[TOOL_CALL]` blocks, parsing and executing tools locally without server-side function-calling support. Uses `[TOOL_CALL]` delimiters instead of `<tool_call>` XML to prevent vLLM's `--tool-call-parser qwen3_xml` stop-token interception.
|
|
89
91
|
|
|
90
92
|
* **Deterministic SDLC Workflow:** Enforces a 6-phase state machine (`idle` → `plan` → `implement` → `test` → `verify` → `done`) to prevent unapproved code changes, hallucinated test passes, or unverified implementations.
|
|
91
93
|
|
|
94
|
+
* **Interactive Safety Interventions:** When execution checkers flag a command — opening local sockets, inspecting HIL testbed hardware paths, or writing outside the working directory — the engine raises a structured `{ status, reason, risk_score, proposed_remediation }` payload rather than crashing. In the TUI, blocked operations surface a `BlockConfirmMenu` (`[A] Approve for Session | [S] Run in Laya Sandbox | [C] Cancel`) wired directly into the Ink event loop. Session approvals are cached so the same operation is not re-prompted within a session. Headless pipe mode auto-denies without prompting.
|
|
95
|
+
|
|
96
|
+
* **Runtime Visibility:** `RuntimeHealthMonitor` probes active inference endpoints (1s HEAD request) and checks Laya sidecar liveness before each session. The TUI status header shows active model, endpoint health and latency, sidecar PID, and context usage percentage — so researchers have immediate visual confirmation of whether the target backend is an internal local host or an external cloud endpoint before uploading grid telemetry.
|
|
97
|
+
|
|
92
98
|
* **Interactive TUI & Pipe Support:** Rich Ink-based TUI (`bin/nehanda-ui.mjs`) for interactive development sessions, with headless pipe-mode support (`bin/agent.mjs`) for acceptance testing and automation.
|
|
93
99
|
|
|
94
100
|
* **Multi-Provider Switching:** Seamlessly switch between Nehanda 27B, local LM Studio, Ollama instances over LAN, Anthropic Claude, or any OpenAI-compatible API using `/model`.
|
|
@@ -103,10 +109,62 @@ To connect to an Ollama instance running on your network:
|
|
|
103
109
|
|
|
104
110
|
---
|
|
105
111
|
|
|
112
|
+
## ODSE Energy Normalization
|
|
113
|
+
|
|
114
|
+
`lib/energy-middleware.mjs` sits between MCP tool responses and the model. When an energy data MCP server returns a payload, the middleware resolves the OEM brand and normalizes the data to the [ODS-E](https://ona-protocol.org) schema via `lib/scripts/odse-transform.py` before the model sees it. This means the model always works with a consistent normalized schema regardless of which inverter brand, BESS, or SCADA system the data originated from.
|
|
115
|
+
|
|
116
|
+
### OEM Detection
|
|
117
|
+
|
|
118
|
+
Brand resolution happens in two passes:
|
|
119
|
+
|
|
120
|
+
1. **Name-based:** The MCP server name is parsed for a known OEM key. `energy-huawei`, `mcp-solaredge`, `power-sungrow-v2` all resolve correctly. Any prefix before the OEM segment is stripped.
|
|
121
|
+
2. **Payload fingerprinting:** If the server name doesn't resolve, field signatures are matched against known schemas — e.g. `{ inverter_state, run_state }` → Huawei FusionSolar; `{ end_at, wh_del, devices_reporting }` → Enphase Envoy; `{ data: { telemetries } }` → SolarEdge.
|
|
122
|
+
|
|
123
|
+
If neither pass identifies the OEM, the payload is returned unchanged (pass-through).
|
|
124
|
+
|
|
125
|
+
### Supported OEMs
|
|
126
|
+
|
|
127
|
+
Huawei FusionSolar, Enphase Envoy, SolarEdge, Fronius, Solarman, SMA, Solis, Sungrow / iSolarCloud, Sungrow BESS / PowerTitan, BYD BESS, Fimer / AuroraVision, SolaxCloud, Eskom (portal, AMR, NRS049), Vestas, Siemens Gamesa, Nordex, Terraco, and generic CSV.
|
|
128
|
+
|
|
129
|
+
### MCP Server Naming Convention
|
|
130
|
+
|
|
131
|
+
Name energy MCP servers with the OEM as a segment in the server name:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"mcpServers": {
|
|
136
|
+
"energy-huawei": { "command": "...", "args": ["..."] },
|
|
137
|
+
"energy-solaredge": { "command": "...", "args": ["..."] }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
No additional configuration is required — normalization is automatic.
|
|
143
|
+
|
|
144
|
+
### `odse-transform.py` Output Formats
|
|
145
|
+
|
|
146
|
+
The transform script accepts a `--format` flag for direct use:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
echo '<payload>' | python3 lib/scripts/odse-transform.py --source huawei --format table
|
|
150
|
+
echo '<payload>' | python3 lib/scripts/odse-transform.py --source solaredge --format ndjson
|
|
151
|
+
echo '<payload>' | python3 lib/scripts/odse-transform.py --source sungrow --format summary
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Format | Output | Default when |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `table` | Box-drawing ASCII table with column headers and row count | stdout is a TTY |
|
|
157
|
+
| `ndjson` | One JSON object per line | stdout is piped |
|
|
158
|
+
| `summary` | Metadata header (`rows`, `shape`, `source`) + JSON array | explicit flag only |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
106
162
|
## Jev-Mem Architecture
|
|
107
163
|
|
|
108
164
|
`nehanda-cli` implements the memory architecture from **"System-One-Controlled Agentic Memory for Efficient AI Agents"** (Jiang, Li & Li, UT Dallas — [arXiv:2609.23986](https://arxiv.org/abs/2609.23986)). The core insight: the frequent, structured decisions of memory management — typing, relation judgment, retrieval routing, sufficiency assessment — don't need an autoregressive LLM. They need a fast, calibrated System-1 model.
|
|
109
165
|
|
|
166
|
+
In the context of energy systems research, this matters directly: long multi-step sessions involving SCADA data ingestion, grid simulation, and iterative code generation accumulate enough context to trigger compaction. Standard sliding-window compaction indiscriminately discards ODSE schema mappings, asset capacity limits, and simulation bounds alongside routine tool outputs — causing the model to hallucinate parameters on subsequent turns. Jev-Mem's pinned context layer prevents this: domain invariants are stored in `pinned_context` and reattached verbatim before every System-2 prompt, surviving arbitrarily many compaction cycles.
|
|
167
|
+
|
|
110
168
|
The System-1 control plane is [**Laya**](https://huggingface.co/convaiinnovations/laya) — a 421M-parameter, non-autoregressive decision model (open weights, Apache 2.0) that answers typed questions in a single forward pass (~33 ms on GPU, ~200 ms on CPU). It never generates text, so there is nothing to parse and nothing to hallucinate.
|
|
111
169
|
|
|
112
170
|
```
|
|
@@ -135,6 +193,7 @@ Observation (user / tool / assistant turn)
|
|
|
135
193
|
│
|
|
136
194
|
▼
|
|
137
195
|
┌── SYSTEM 2 PROMPT ────────────────────────────────────────────┐
|
|
196
|
+
│ [PINNED DOMAIN INVARIANTS] (ODSE schema, asset specs, ...) │
|
|
138
197
|
│ [CANONICAL SYSTEM 1 STATE] (task_status, file_target, ...) │
|
|
139
198
|
│ [RETRIEVED EVIDENCE] (top-K memories, not raw transcript) │
|
|
140
199
|
│ last 2 turns + last failed tool result │
|
|
@@ -164,7 +223,7 @@ On first startup, `lib/scripts/ensure-laya-env.sh` creates a dedicated venv at `
|
|
|
164
223
|
|
|
165
224
|
### Memory plane (SQLite)
|
|
166
225
|
|
|
167
|
-
The memory plane extends the existing `memories` table and adds `memory_edges` and `
|
|
226
|
+
The memory plane extends the existing `memories` table and adds `memory_edges`, `laya_jev_state`, and `pinned_context`. All data lives in the same local SQLite database as the rest of the session state (`~/.config/nehanda/ona-session.db`).
|
|
168
227
|
|
|
169
228
|
| Table | Purpose |
|
|
170
229
|
|---|---|
|
|
@@ -172,6 +231,7 @@ The memory plane extends the existing `memories` table and adds `memory_edges` a
|
|
|
172
231
|
| `memories_fts` | FTS5 index over memory content for lexical anchor retrieval |
|
|
173
232
|
| `memory_edges` | Multi-relational edges — same node pair may have independent semantic, temporal, causal, and entity edges |
|
|
174
233
|
| `laya_jev_state` | Session control block: `task_status`, `file_target`, `escalation_risk`, `confidence_score` |
|
|
234
|
+
| `pinned_context` | Session-scoped domain invariants keyed by string — ODSE schema mappings, asset specs, simulation bounds. Never distilled by compaction; reattached verbatim before every System-2 prompt. |
|
|
175
235
|
|
|
176
236
|
---
|
|
177
237
|
|
|
@@ -388,6 +448,7 @@ Session state is persisted locally at `~/.config/nehanda/ona-session.db`. Key ta
|
|
|
388
448
|
* `memories`: Jev-Mem canonical node store with overlapping type scores and entity provenance.
|
|
389
449
|
* `memory_edges`: Multi-relational graph edges (semantic / temporal / causal / entity).
|
|
390
450
|
* `laya_jev_state`: System-1 session control block (task_status, file_target, escalation_risk).
|
|
451
|
+
* `pinned_context`: Session-scoped domain invariants (ODSE schema mappings, asset specs, simulation bounds) that survive compaction unchanged.
|
|
391
452
|
|
|
392
453
|
---
|
|
393
454
|
|
|
@@ -414,6 +475,15 @@ node tests/unit/compact_jev_behavioral.mjs
|
|
|
414
475
|
node tests/unit/orchestrate_jev_hotpath_behavioral.mjs
|
|
415
476
|
```
|
|
416
477
|
|
|
478
|
+
Run the UX improvement behavioral suites:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
node tests/unit/gap1_runtime_visibility_behavioral.mjs
|
|
482
|
+
node tests/unit/gap2_output_formatting_behavioral.mjs
|
|
483
|
+
node tests/unit/gap3_safety_intervention_behavioral.mjs
|
|
484
|
+
node tests/unit/gap4_pinned_compaction_behavioral.mjs
|
|
485
|
+
```
|
|
486
|
+
|
|
417
487
|
---
|
|
418
488
|
|
|
419
489
|
## Changelog
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asobacloud/nehanda",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agentic terminal REPL and single-process engine for governed AI deep research and software development. Connects to Nehanda v3, Ollama, LM Studio, or any OpenAI-compatible API.",
|
|
6
6
|
"engines": {
|