@hilbras/omninode 2.0.0-alpha.7 → 2.0.0-alpha.8

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 CHANGED
@@ -18,9 +18,9 @@ existing intelligence; it does not try to become another model.
18
18
 
19
19
  ## Status
20
20
 
21
- **v2.0.0-alpha.7 — v2 Phase 7: OmniHilbras Integration v2**
21
+ **v2.0.0-alpha.8 — v2 Phase 8: Remembera Integration v2**
22
22
  (the reliability & interoperability line: architecture → execution →
23
- protocol → adapters → pipelines → providers → OmniHilbras) (the v2
23
+ protocol → adapters → pipelines → providers → OmniHilbras → memory) (the v2
24
24
  roadmap is [docs/ROADMAP_V2.md](docs/ROADMAP_V2.md); the v1 plan is complete:
25
25
  the
26
26
  full loop from §28 runs in one command —
@@ -31,6 +31,12 @@ for the plan and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the design).
31
31
 
32
32
  ### What's new in v2 (so far)
33
33
 
34
+ - **Phase 8 — Remembera / Memory v2**: the memory contract is formalized
35
+ (`retrieve` / `store` / `search` / `metadata` with v1 `query` / `write`
36
+ aliases kept working), memory gains structured **categories** (decisions,
37
+ problems, conventions, tasks…), retrieval is **task-oriented and budgeted**
38
+ (never "load all memory"), and `memory.required: true` makes backend
39
+ failures fatal instead of tolerated. Details: [MEMORY.md](docs/MEMORY.md).
34
40
  - **Phase 7 — OmniHilbras Integration v2**: the dedicated adapter now
35
41
  captures gateway-level metadata (version, tier, region) and attaches it
36
42
  to discovered models, honors provider-level `timeout_ms`, and supports
@@ -333,7 +339,7 @@ required for the core engine.
333
339
  - [Architecture](docs/ARCHITECTURE.md) — module map, workflow, local state
334
340
  - [Security model](docs/SECURITY.md) — secrets, process execution, env isolation, audit log
335
341
  - [API stability policy](docs/API_STABILITY.md) and [public API inventory](docs/API.md)
336
- - [Agent Protocol v2 spec](docs/PROTOCOL.md) · [Providers](docs/PROVIDERS.md) · [Deprecations](docs/DEPRECATIONS.md) · [Changelog](CHANGELOG.md) · [v2 roadmap](docs/ROADMAP_V2.md)
342
+ - [Agent Protocol v2 spec](docs/PROTOCOL.md) · [Providers](docs/PROVIDERS.md) · [Memory](docs/MEMORY.md) · [Deprecations](docs/DEPRECATIONS.md) · [Changelog](CHANGELOG.md) · [v2 roadmap](docs/ROADMAP_V2.md)
337
343
  - [Example project](examples/demo/) — a runnable end-to-end demo (`./demo.sh`)
338
344
 
339
345
  Prefer a container? `docker build -t omninode .` then
package/dist/cli/index.js CHANGED
@@ -6,7 +6,7 @@ import { realpathSync } from "fs";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/version.ts
9
- var OMNINODE_VERSION = "2.0.0-alpha.7";
9
+ var OMNINODE_VERSION = "2.0.0-alpha.8";
10
10
 
11
11
  // src/errors/index.ts
12
12
  var OmniNodeError = class extends Error {
@@ -134,7 +134,8 @@ var projectConfigSchema = z.object({
134
134
  provider: z.string().min(1).default("local"),
135
135
  base_url: z.string().url().optional(),
136
136
  api_key_env_var: z.string().min(1).optional(),
137
- timeout_ms: z.number().int().positive().optional()
137
+ /** When true, memory failures fail tasks instead of being tolerated (§12). */
138
+ required: z.boolean().optional()
138
139
  }).strict().optional()
139
140
  }).strict();
140
141
  var appConfigSchema = z.object({
@@ -216,7 +217,8 @@ function toAppConfig(yaml) {
216
217
  memory: {
217
218
  provider: yaml.project.memory.provider,
218
219
  baseUrl: yaml.project.memory.base_url,
219
- apiKeyEnvVar: yaml.project.memory.api_key_env_var
220
+ apiKeyEnvVar: yaml.project.memory.api_key_env_var,
221
+ required: yaml.project.memory.required
220
222
  }
221
223
  } : {}
222
224
  },
@@ -2490,6 +2492,23 @@ var FileAuditLog = class {
2490
2492
  }
2491
2493
  };
2492
2494
 
2495
+ // src/memory/base.ts
2496
+ function defaultSearch(provider, text, options = {}) {
2497
+ return provider.retrieve({
2498
+ text,
2499
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
2500
+ ...options.limit !== void 0 ? { limit: options.limit } : {}
2501
+ });
2502
+ }
2503
+ function defaultMetadata(name, options = {}) {
2504
+ return {
2505
+ name,
2506
+ capabilities: options.capabilities ?? ["retrieve", "store", "search"],
2507
+ relevanceRanking: options.relevanceRanking ?? false,
2508
+ ...options.backend !== void 0 ? { backend: options.backend } : {}
2509
+ };
2510
+ }
2511
+
2493
2512
  // src/memory/local.ts
2494
2513
  var STOPWORDS = /* @__PURE__ */ new Set([
2495
2514
  "the",
@@ -2552,9 +2571,27 @@ var LocalMemoryProvider = class extends JsonFileStore {
2552
2571
  constructor(directory) {
2553
2572
  super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
2554
2573
  }
2555
- async query(query) {
2574
+ metadata() {
2575
+ return defaultMetadata(this.name, {
2576
+ capabilities: ["retrieve", "store", "search", "categories", "tags"],
2577
+ relevanceRanking: true,
2578
+ backend: { kind: "json-file", deterministicScoring: true }
2579
+ });
2580
+ }
2581
+ search(text, options = {}) {
2582
+ return defaultSearch(this, text, options);
2583
+ }
2584
+ /** @deprecated v1 alias of {@link retrieve}. */
2585
+ query(query) {
2586
+ return this.retrieve(query);
2587
+ }
2588
+ /** @deprecated v1 alias of {@link store}. */
2589
+ write(entry2) {
2590
+ return this.store(entry2);
2591
+ }
2592
+ async retrieve(query) {
2556
2593
  const entries = await this.readAll();
2557
- const filtered = entries.filter((entry2) => query.scope ? entry2.scope === query.scope : true).filter(
2594
+ const filtered = entries.filter((entry2) => query.scope ? entry2.scope === query.scope : true).filter((entry2) => query.category ? entry2.category === query.category : true).filter(
2558
2595
  (entry2) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry2.tags ?? []).includes(tag)) : true
2559
2596
  );
2560
2597
  if (!query.text || query.text.trim().length === 0) {
@@ -2563,7 +2600,7 @@ var LocalMemoryProvider = class extends JsonFileStore {
2563
2600
  const queryKeywords = keywords(query.text);
2564
2601
  return filtered.map((entry2) => ({ entry: entry2, score: relevanceScore(queryKeywords, entry2) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry: entry2 }) => entry2);
2565
2602
  }
2566
- write(entry2) {
2603
+ store(entry2) {
2567
2604
  return this.mutate((entries) => {
2568
2605
  const index = entries.findIndex((e) => e.key === entry2.key);
2569
2606
  const stamped = {
@@ -2594,11 +2631,40 @@ var RememberaMemoryProvider = class {
2594
2631
  this.env = config.env ?? process.env;
2595
2632
  this.log = log.child({ component: "remembera" });
2596
2633
  }
2634
+ get authenticationConfigured() {
2635
+ return this.apiKeyEnvVar === void 0 || Boolean(this.env[this.apiKeyEnvVar]);
2636
+ }
2597
2637
  headers() {
2598
2638
  const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
2599
2639
  return key ? { authorization: `Bearer ${key}` } : {};
2600
2640
  }
2601
- async query(query) {
2641
+ /** Search delegates to the gateway's relevance query. */
2642
+ search(text, options = {}) {
2643
+ return defaultSearch(
2644
+ this,
2645
+ text,
2646
+ {
2647
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
2648
+ ...options.limit !== void 0 ? { limit: options.limit } : {}
2649
+ }
2650
+ );
2651
+ }
2652
+ metadata() {
2653
+ return defaultMetadata(this.name, {
2654
+ capabilities: ["retrieve", "store", "search", "categories", "tags"],
2655
+ relevanceRanking: true,
2656
+ backend: { kind: "http", baseUrl: this.baseUrl, authenticated: this.authenticationConfigured }
2657
+ });
2658
+ }
2659
+ /** @deprecated v1 alias of {@link retrieve}. */
2660
+ query(query) {
2661
+ return this.retrieve(query);
2662
+ }
2663
+ /** @deprecated v1 alias of {@link store}. */
2664
+ write(entry2) {
2665
+ return this.store(entry2);
2666
+ }
2667
+ async retrieve(query) {
2602
2668
  const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
2603
2669
  if (status !== 200) {
2604
2670
  throw new MemoryError(`Remembera query returned HTTP ${status}.`);
@@ -2609,7 +2675,7 @@ var RememberaMemoryProvider = class {
2609
2675
  }
2610
2676
  return entries.map((entry2) => this.toEntry(entry2)).filter((entry2) => entry2 !== void 0);
2611
2677
  }
2612
- async write(entry2) {
2678
+ async store(entry2) {
2613
2679
  const { status } = await this.request(`${this.baseUrl}/api/memory`, entry2);
2614
2680
  if (status !== 200 && status !== 201) {
2615
2681
  throw new MemoryError(`Remembera write returned HTTP ${status}.`);
@@ -2644,6 +2710,7 @@ var RememberaMemoryProvider = class {
2644
2710
  content: entry2.content,
2645
2711
  scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
2646
2712
  ...Array.isArray(entry2.tags) ? { tags: entry2.tags.filter((t) => typeof t === "string") } : {},
2713
+ ...typeof entry2.category === "string" ? { category: entry2.category } : {},
2647
2714
  ...typeof entry2.createdAt === "string" ? { createdAt: entry2.createdAt } : {},
2648
2715
  ...typeof entry2.updatedAt === "string" ? { updatedAt: entry2.updatedAt } : {}
2649
2716
  };
@@ -2674,57 +2741,54 @@ function createMemoryProvider(config) {
2674
2741
  }
2675
2742
 
2676
2743
  // src/memory/service.ts
2744
+ var DEFAULT_ENTRY_LIMIT = 8;
2677
2745
  var MAX_CONTEXT_CHARS = 4e3;
2678
2746
  var MemoryService = class {
2679
- constructor(provider, log = logger) {
2747
+ constructor(provider, log = logger, options = {}) {
2680
2748
  this.provider = provider;
2681
2749
  this.log = log.child({ component: "memory", provider: provider.name });
2750
+ this.required = options.required ?? false;
2751
+ this.maxContextChars = options.maxContextChars ?? MAX_CONTEXT_CHARS;
2682
2752
  }
2683
2753
  provider;
2684
2754
  log;
2755
+ required;
2756
+ maxContextChars;
2685
2757
  get providerRef() {
2686
2758
  return this.provider;
2687
2759
  }
2688
2760
  get providerName() {
2689
2761
  return this.provider.name;
2690
2762
  }
2691
- /** Relevant context for a task objective; empty string when nothing matches. */
2692
- async gatherContext(query) {
2763
+ get isRequired() {
2764
+ return this.required;
2765
+ }
2766
+ /** Structured, budgeted context for a task (§12 — avoid loading all memory). */
2767
+ async contextFor(query) {
2768
+ let entries;
2693
2769
  try {
2694
- const entries = await this.provider.query({
2770
+ entries = await this.provider.retrieve({
2695
2771
  text: query.objective,
2696
2772
  ...query.role ? { tags: [query.role] } : {},
2697
- limit: 8
2773
+ limit: query.limit ?? DEFAULT_ENTRY_LIMIT
2698
2774
  });
2699
- if (entries.length === 0) return "";
2700
- const byScope = /* @__PURE__ */ new Map();
2701
- for (const entry2 of entries) {
2702
- byScope.set(entry2.scope, [...byScope.get(entry2.scope) ?? [], entry2]);
2703
- }
2704
- const parts = [];
2705
- for (const scope of ["project", "task", "role", "knowledge"]) {
2706
- const scopeEntries = byScope.get(scope) ?? [];
2707
- if (scopeEntries.length === 0) continue;
2708
- const lines = scopeEntries.map((entry2) => `- ${entry2.content}`).join("\n");
2709
- parts.push(`### Relevant memory (${scope})
2710
- ${lines}`);
2711
- }
2712
- const context = parts.join("\n\n");
2713
- return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
2714
2775
  } catch (error) {
2715
- this.log.warn(
2716
- `Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
2717
- );
2718
- return "";
2776
+ return this.handleFailure("retrieve", error, { text: "", entries: [], truncated: false });
2719
2777
  }
2778
+ return this.format(entries);
2779
+ }
2780
+ /** Prompt-ready context text (v1 convenience wrapper). */
2781
+ async gatherContext(query) {
2782
+ return (await this.contextFor(query)).text;
2720
2783
  }
2721
- /** Records a finished task's outcome — best-effort. */
2784
+ /** Records a finished task's outcome — task history plus "known problems" (§12). */
2722
2785
  async recordTaskOutcome(task) {
2723
2786
  const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
2724
2787
  try {
2725
- await this.provider.write({
2788
+ await this.provider.store({
2726
2789
  key: `task:${task.id}`,
2727
2790
  scope: "task",
2791
+ category: "tasks",
2728
2792
  content: `Objective: ${task.objective}
2729
2793
  Status: ${task.status}
2730
2794
  Outcome: ${outcome}`,
@@ -2735,18 +2799,53 @@ Outcome: ${outcome}`,
2735
2799
  (report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
2736
2800
  );
2737
2801
  for (const { report, finding } of significant) {
2738
- await this.provider.write({
2802
+ await this.provider.store({
2739
2803
  key: `finding:${report.id}:${finding.id}`,
2740
2804
  scope: "project",
2805
+ category: "problems",
2741
2806
  content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
2742
2807
  tags: ["known-problem", report.agent]
2743
2808
  });
2744
2809
  }
2745
2810
  } catch (error) {
2746
- this.log.warn(
2747
- `Memory write failed (continuing): ${error instanceof Error ? error.message : String(error)}`
2811
+ this.handleFailure("store", error, void 0);
2812
+ }
2813
+ }
2814
+ format(entries) {
2815
+ if (entries.length === 0) return { text: "", entries, truncated: false };
2816
+ const byScope = /* @__PURE__ */ new Map();
2817
+ for (const entry2 of entries) {
2818
+ byScope.set(entry2.scope, [...byScope.get(entry2.scope) ?? [], entry2]);
2819
+ }
2820
+ const parts = [];
2821
+ for (const scope of ["project", "task", "role", "knowledge"]) {
2822
+ const scopeEntries = byScope.get(scope) ?? [];
2823
+ if (scopeEntries.length === 0) continue;
2824
+ const lines = scopeEntries.map((entry2) => {
2825
+ const category = entry2.category ? `[${entry2.category}] ` : "";
2826
+ return `- ${category}${entry2.content}`;
2827
+ }).join("\n");
2828
+ parts.push(`### Relevant memory (${scope})
2829
+ ${lines}`);
2830
+ }
2831
+ let text = parts.join("\n\n");
2832
+ const truncated = text.length > this.maxContextChars;
2833
+ if (truncated) text = `${text.slice(0, this.maxContextChars - 1)}\u2026`;
2834
+ return { text, entries, truncated };
2835
+ }
2836
+ /**
2837
+ * Failure policy (§12): continue unless memory is explicitly required.
2838
+ */
2839
+ handleFailure(operation, error, fallback) {
2840
+ const message = error instanceof Error ? error.message : String(error);
2841
+ if (this.required) {
2842
+ throw new MemoryError(
2843
+ `Memory is marked required, but ${operation} failed: ${message}`,
2844
+ { cause: error }
2748
2845
  );
2749
2846
  }
2847
+ this.log.warn(`Memory ${operation} failed (continuing without memory): ${message}`);
2848
+ return fallback ?? { text: "", entries: [], truncated: false };
2750
2849
  }
2751
2850
  };
2752
2851
 
@@ -2780,7 +2879,11 @@ function createTaskEngine(config, log = logger, directory) {
2780
2879
  agents,
2781
2880
  roles,
2782
2881
  store: new FileTaskStore(directory),
2783
- ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
2882
+ ...config.project.memory !== void 0 ? {
2883
+ memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
2884
+ required: config.project.memory.required
2885
+ })
2886
+ } : {},
2784
2887
  audit: new FileAuditLog(),
2785
2888
  log
2786
2889
  });
@@ -4056,7 +4159,11 @@ function buildPipelineEngine(config, log = logger) {
4056
4159
  agents,
4057
4160
  roles,
4058
4161
  store: new FileTaskStore(),
4059
- ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
4162
+ ...config.project.memory !== void 0 ? {
4163
+ memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
4164
+ required: config.project.memory.required
4165
+ })
4166
+ } : {},
4060
4167
  audit: new FileAuditLog(),
4061
4168
  log
4062
4169
  });