@learningnodes/elen 0.1.1 → 0.1.2

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/dist/client.d.ts CHANGED
@@ -1,15 +1,18 @@
1
- import { type DecisionRecord, type ConstraintSet } from '@learningnodes/elen-core';
1
+ import { type MinimalDecisionRecord, type DecisionRecord, type ConstraintSet } from '@learningnodes/elen-core';
2
2
  import type { StorageAdapter } from './storage';
3
- import type { CompetencyProfileResult, CommitDecisionInput, SearchOptions } from './types';
3
+ import type { CompetencyProfileResult, CommitDecisionInput, LogDecisionInput, SearchOptions } from './types';
4
4
  export declare class ElenClient {
5
5
  private readonly agentId;
6
6
  private readonly storage;
7
7
  constructor(agentId: string, storage: StorageAdapter);
8
- commitDecision(input: CommitDecisionInput): Promise<DecisionRecord>;
9
- supersedeDecision(oldDecisionId: string, input: CommitDecisionInput): Promise<DecisionRecord>;
10
- suggest(opts: SearchOptions): Promise<Partial<DecisionRecord>[]>;
8
+ logDecision(input: LogDecisionInput): Promise<DecisionRecord>;
9
+ commitDecision(input: CommitDecisionInput): Promise<MinimalDecisionRecord>;
10
+ supersedeDecision(oldDecisionId: string, input: CommitDecisionInput): Promise<MinimalDecisionRecord>;
11
+ searchRecords(opts: SearchOptions): Promise<(import("@learningnodes/elen-core").DecisionRecordV0 | MinimalDecisionRecord)[]>;
12
+ searchPrecedents(query: string, opts?: SearchOptions): Promise<(import("@learningnodes/elen-core").DecisionRecordV0 | MinimalDecisionRecord)[]>;
13
+ suggest(opts: SearchOptions): Promise<Array<Partial<MinimalDecisionRecord>>>;
11
14
  expand(decisionId: string): Promise<{
12
- record: DecisionRecord;
15
+ record: MinimalDecisionRecord;
13
16
  constraints: ConstraintSet;
14
17
  } | null>;
15
18
  getCompetencyProfile(): Promise<CompetencyProfileResult>;
package/dist/client.js CHANGED
@@ -8,9 +8,71 @@ class ElenClient {
8
8
  this.agentId = agentId;
9
9
  this.storage = storage;
10
10
  }
11
+ async logDecision(input) {
12
+ if (input.constraints.length === 0)
13
+ throw new Error('Decision must include at least one constraint');
14
+ if (input.evidence.length === 0)
15
+ throw new Error('Decision must include at least one evidence');
16
+ const decisionId = (0, id_1.createDecisionId)(input.domain);
17
+ const now = new Date().toISOString();
18
+ const constraintsSnapshot = input.constraints.map((description, i) => ({
19
+ constraint_id: (0, id_1.createId)(`con${i}`),
20
+ decision_id: decisionId,
21
+ type: 'requirement',
22
+ description,
23
+ locked: false
24
+ }));
25
+ const evidenceSnapshot = input.evidence.map((e, i) => ({
26
+ evidence_id: (0, id_1.createId)(`evd${i}`),
27
+ decision_id: decisionId,
28
+ type: input.linkedPrecedents?.[i] ? 'precedent' : 'observation',
29
+ claim: e,
30
+ proof: e,
31
+ confidence: input.confidence?.[i] ?? 0.8,
32
+ linked_precedent: input.linkedPrecedents?.[i]
33
+ }));
34
+ const checksSnapshot = evidenceSnapshot.map((e, i) => ({
35
+ check_id: (0, id_1.createId)(`chk${i}`),
36
+ decision_id: decisionId,
37
+ claim: e.claim,
38
+ result: 'pass',
39
+ evidence_ids: [e.evidence_id],
40
+ epistemic_type: (0, elen_core_1.classifyEpistemicType)(e),
41
+ confidence: e.confidence
42
+ }));
43
+ const record = {
44
+ record_id: (0, id_1.createId)('rec'),
45
+ decision_id: decisionId,
46
+ agent_id: this.agentId,
47
+ question: input.question,
48
+ answer: input.answer,
49
+ constraints_snapshot: constraintsSnapshot,
50
+ evidence_snapshot: evidenceSnapshot,
51
+ checks_snapshot: checksSnapshot,
52
+ confidence: evidenceSnapshot.reduce((a, e) => a + e.confidence, 0) / evidenceSnapshot.length,
53
+ validation_type: 'self',
54
+ domain: input.domain,
55
+ tags: [],
56
+ published_at: now,
57
+ expires_at: null
58
+ };
59
+ await this.storage.saveDecision?.({
60
+ decision_id: decisionId,
61
+ agent_id: this.agentId,
62
+ question: input.question,
63
+ domain: input.domain,
64
+ status: 'validated',
65
+ constraints: constraintsSnapshot,
66
+ evidence: evidenceSnapshot,
67
+ checks: checksSnapshot,
68
+ created_at: now,
69
+ parent_prompt: input.parentPrompt
70
+ });
71
+ await this.storage.saveLegacyRecord?.(record);
72
+ return record;
73
+ }
11
74
  async commitDecision(input) {
12
75
  const now = new Date().toISOString();
13
- // 1. Resolve Constraints (Deterministic Hashing server-side)
14
76
  const constraintSetId = (0, id_1.createConstraintSetId)(input.constraints);
15
77
  const existingConstraints = await this.storage.getConstraintSet(constraintSetId);
16
78
  if (!existingConstraints) {
@@ -22,10 +84,10 @@ class ElenClient {
22
84
  elen_core_1.constraintSetSchema.parse(newSet);
23
85
  await this.storage.saveConstraintSet(newSet);
24
86
  }
25
- // 2. Build the Minimal Decision Atom
26
87
  const record = {
27
88
  decision_id: (0, id_1.createDecisionId)(input.domain),
28
89
  q_id: (0, id_1.createId)('q'),
90
+ question_text: input.question,
29
91
  decision_text: input.decisionText,
30
92
  constraint_set_id: constraintSetId,
31
93
  refs: input.refs ?? [],
@@ -35,30 +97,36 @@ class ElenClient {
35
97
  agent_id: this.agentId,
36
98
  domain: input.domain
37
99
  };
38
- elen_core_1.decisionRecordSchema.parse(record);
100
+ elen_core_1.minimalDecisionRecordSchema.parse(record);
39
101
  await this.storage.saveRecord(record);
40
102
  return record;
41
103
  }
42
104
  async supersedeDecision(oldDecisionId, input) {
43
- // 1. Mark old as superseded
44
105
  const oldRecord = await this.storage.getRecord(oldDecisionId);
45
- if (oldRecord) {
106
+ if (oldRecord && 'status' in oldRecord) {
46
107
  oldRecord.status = 'superseded';
47
108
  await this.storage.saveRecord(oldRecord);
48
109
  }
49
- // 2. Commit new
50
- return this.commitDecision({
51
- ...input,
52
- supersedesId: oldDecisionId
53
- });
110
+ return this.commitDecision({ ...input, supersedesId: oldDecisionId });
111
+ }
112
+ async searchRecords(opts) {
113
+ return this.storage.searchRecords(opts);
114
+ }
115
+ async searchPrecedents(query, opts = {}) {
116
+ const direct = await this.storage.searchRecords({ ...opts, query });
117
+ if (direct.length > 0)
118
+ return direct;
119
+ return this.storage.searchRecords({ ...opts, limit: opts.limit ?? 5 });
54
120
  }
55
121
  async suggest(opts) {
56
122
  const fullRecords = await this.storage.searchRecords(opts);
57
- // Pointer-first retrieval (minimal payload)
58
- return fullRecords.map(r => ({
123
+ return fullRecords
124
+ .filter((r) => 'decision_text' in r)
125
+ .map((r) => ({
59
126
  decision_id: r.decision_id,
60
127
  status: r.status,
61
128
  decision_text: r.decision_text,
129
+ question_text: r.question_text,
62
130
  constraint_set_id: r.constraint_set_id,
63
131
  refs: r.refs,
64
132
  supersedes_id: r.supersedes_id
@@ -66,7 +134,7 @@ class ElenClient {
66
134
  }
67
135
  async expand(decisionId) {
68
136
  const record = await this.storage.getRecord(decisionId);
69
- if (!record)
137
+ if (!record || !('constraint_set_id' in record))
70
138
  return null;
71
139
  const constraints = await this.storage.getConstraintSet(record.constraint_set_id);
72
140
  if (!constraints)
package/dist/index.d.ts CHANGED
@@ -1,13 +1,16 @@
1
- import type { ElenConfig, CommitDecisionInput, SearchOptions } from './types';
1
+ import type { ElenConfig, CommitDecisionInput, LogDecisionInput, SearchOptions } from './types';
2
2
  export declare class Elen {
3
3
  private readonly client;
4
4
  constructor(config: ElenConfig);
5
5
  private createStorage;
6
- commitDecision(input: CommitDecisionInput): Promise<import("@learningnodes/elen-core").DecisionRecord>;
7
- supersedeDecision(oldDecisionId: string, input: CommitDecisionInput): Promise<import("@learningnodes/elen-core").DecisionRecord>;
8
- suggest(opts: SearchOptions): Promise<Partial<import("@learningnodes/elen-core").DecisionRecord>[]>;
6
+ logDecision(input: LogDecisionInput): Promise<import("@learningnodes/elen-core").DecisionRecordV0>;
7
+ commitDecision(input: CommitDecisionInput): Promise<import("@learningnodes/elen-core").MinimalDecisionRecord>;
8
+ supersedeDecision(oldDecisionId: string, input: CommitDecisionInput): Promise<import("@learningnodes/elen-core").MinimalDecisionRecord>;
9
+ searchRecords(opts: SearchOptions): Promise<(import("@learningnodes/elen-core").DecisionRecordV0 | import("@learningnodes/elen-core").MinimalDecisionRecord)[]>;
10
+ searchPrecedents(query: string, opts?: SearchOptions): Promise<(import("@learningnodes/elen-core").DecisionRecordV0 | import("@learningnodes/elen-core").MinimalDecisionRecord)[]>;
11
+ suggest(opts: SearchOptions): Promise<Partial<import("@learningnodes/elen-core").MinimalDecisionRecord>[]>;
9
12
  expand(decisionId: string): Promise<{
10
- record: import("@learningnodes/elen-core").DecisionRecord;
13
+ record: import("@learningnodes/elen-core").MinimalDecisionRecord;
11
14
  constraints: import("@learningnodes/elen-core").ConstraintSet;
12
15
  } | null>;
13
16
  getCompetencyProfile(): Promise<import("@learningnodes/elen-core").CompetencyProfile>;
package/dist/index.js CHANGED
@@ -24,16 +24,25 @@ class Elen {
24
24
  }
25
25
  createStorage(config) {
26
26
  if (config.storage === 'sqlite') {
27
- return new storage_1.SQLiteStorage(config.sqlitePath ?? 'elen.db', config.projectId);
27
+ return new storage_1.SQLiteStorage(config.sqlitePath ?? 'elen.db', config.projectId, config.defaultProjectIsolation ?? 'strict');
28
28
  }
29
29
  return new storage_1.InMemoryStorage();
30
30
  }
31
+ async logDecision(input) {
32
+ return this.client.logDecision(input);
33
+ }
31
34
  async commitDecision(input) {
32
35
  return this.client.commitDecision(input);
33
36
  }
34
37
  async supersedeDecision(oldDecisionId, input) {
35
38
  return this.client.supersedeDecision(oldDecisionId, input);
36
39
  }
40
+ async searchRecords(opts) {
41
+ return this.client.searchRecords(opts);
42
+ }
43
+ async searchPrecedents(query, opts = {}) {
44
+ return this.client.searchPrecedents(query, opts);
45
+ }
37
46
  async suggest(opts) {
38
47
  return this.client.suggest(opts);
39
48
  }
@@ -1,11 +1,13 @@
1
- import type { CompetencyProfile, DecisionRecord, ConstraintSet } from '@learningnodes/elen-core';
1
+ import type { CompetencyProfile, ConstraintSet, DecisionContext, DecisionRecord, MinimalDecisionRecord } from '@learningnodes/elen-core';
2
2
  import type { SearchOptions } from '../types';
3
3
  export interface StorageAdapter {
4
+ saveDecision?(decision: DecisionContext): Promise<void>;
4
5
  saveConstraintSet(constraintSet: ConstraintSet): Promise<void>;
5
6
  getConstraintSet(id: string): Promise<ConstraintSet | null>;
6
- saveRecord(record: DecisionRecord): Promise<void>;
7
- getRecord(recordId: string): Promise<DecisionRecord | null>;
8
- searchRecords(opts: SearchOptions): Promise<DecisionRecord[]>;
9
- getAgentDecisions(agentId: string, domain?: string): Promise<DecisionRecord[]>;
7
+ saveRecord(record: MinimalDecisionRecord | DecisionRecord): Promise<void>;
8
+ saveLegacyRecord?(record: DecisionRecord): Promise<void>;
9
+ getRecord(recordId: string): Promise<MinimalDecisionRecord | DecisionRecord | null>;
10
+ searchRecords(opts: SearchOptions): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
11
+ getAgentDecisions(agentId: string, domain?: string): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
10
12
  getCompetencyProfile(agentId: string): Promise<CompetencyProfile>;
11
13
  }
@@ -1,14 +1,17 @@
1
- import type { CompetencyProfile, ConstraintSet, DecisionRecord } from '@learningnodes/elen-core';
1
+ import type { CompetencyProfile, ConstraintSet, DecisionContext, DecisionRecord, MinimalDecisionRecord } from '@learningnodes/elen-core';
2
2
  import type { SearchOptions } from '../types';
3
3
  import type { StorageAdapter } from './interface';
4
4
  export declare class InMemoryStorage implements StorageAdapter {
5
5
  private readonly constraintSets;
6
6
  private readonly records;
7
+ private readonly decisions;
8
+ saveDecision(decision: DecisionContext): Promise<void>;
7
9
  saveConstraintSet(constraintSet: ConstraintSet): Promise<void>;
8
10
  getConstraintSet(id: string): Promise<ConstraintSet | null>;
9
- saveRecord(record: DecisionRecord): Promise<void>;
10
- getRecord(recordId: string): Promise<DecisionRecord | null>;
11
- searchRecords(opts: SearchOptions): Promise<DecisionRecord[]>;
12
- getAgentDecisions(agentId: string, domain?: string): Promise<DecisionRecord[]>;
11
+ saveRecord(record: MinimalDecisionRecord | DecisionRecord): Promise<void>;
12
+ saveLegacyRecord(record: DecisionRecord): Promise<void>;
13
+ getRecord(recordId: string): Promise<MinimalDecisionRecord | DecisionRecord | null>;
14
+ searchRecords(opts: SearchOptions): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
15
+ getAgentDecisions(agentId: string, domain?: string): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
13
16
  getCompetencyProfile(agentId: string): Promise<CompetencyProfile>;
14
17
  }
@@ -5,65 +5,54 @@ class InMemoryStorage {
5
5
  constructor() {
6
6
  this.constraintSets = new Map();
7
7
  this.records = new Map();
8
- }
9
- async saveConstraintSet(constraintSet) {
10
- if (!this.constraintSets.has(constraintSet.constraint_set_id)) {
11
- this.constraintSets.set(constraintSet.constraint_set_id, constraintSet);
12
- }
13
- }
14
- async getConstraintSet(id) {
15
- return this.constraintSets.get(id) ?? null;
16
- }
17
- async saveRecord(record) {
18
- this.records.set(record.decision_id, record);
19
- }
20
- async getRecord(recordId) {
21
- return this.records.get(recordId) ?? null;
22
- }
8
+ this.decisions = new Map();
9
+ }
10
+ async saveDecision(decision) { this.decisions.set(decision.decision_id, decision); }
11
+ async saveConstraintSet(constraintSet) { if (!this.constraintSets.has(constraintSet.constraint_set_id))
12
+ this.constraintSets.set(constraintSet.constraint_set_id, constraintSet); }
13
+ async getConstraintSet(id) { return this.constraintSets.get(id) ?? null; }
14
+ async saveRecord(record) { this.records.set(("record_id" in record ? record.record_id : record.decision_id), record); }
15
+ async saveLegacyRecord(record) { this.records.set(record.record_id, record); }
16
+ async getRecord(recordId) { return this.records.get(recordId) ?? null; }
23
17
  async searchRecords(opts) {
24
18
  let results = Array.from(this.records.values());
25
- if (opts.domain) {
26
- results = results.filter((record) => record.domain === opts.domain);
19
+ if (opts.domain)
20
+ results = results.filter((r) => r.domain === opts.domain);
21
+ if (typeof opts.minConfidence === 'number')
22
+ results = results.filter((r) => ('confidence' in r ? r.confidence >= opts.minConfidence : true));
23
+ if (opts.parentPrompt) {
24
+ const needle = opts.parentPrompt.toLowerCase();
25
+ results = results.filter((r) => {
26
+ const ctx = this.decisions.get(r.decision_id);
27
+ return ctx?.parent_prompt?.toLowerCase().includes(needle) ?? false;
28
+ });
27
29
  }
28
30
  if (opts.query) {
29
31
  const needle = opts.query.toLowerCase();
30
- results = results.filter((record) => {
31
- const haystack = [
32
- record.decision_text,
33
- record.domain,
34
- record.q_id
35
- ].join(' ').toLowerCase();
36
- return haystack.includes(needle);
32
+ results = results.filter((r) => {
33
+ const haystack = 'decision_text' in r ? `${r.question_text ?? ''} ${r.decision_text} ${r.domain}` : `${r.question} ${r.answer} ${r.domain} ${r.constraints_snapshot.map(c => c.description).join(' ')} ${r.evidence_snapshot.map(e => `${e.claim} ${e.proof}`).join(' ')}`;
34
+ return haystack.toLowerCase().includes(needle);
37
35
  });
38
36
  }
39
37
  const limit = opts.limit ?? results.length;
40
- return results.sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, limit);
38
+ return results.slice(0, limit);
41
39
  }
42
40
  async getAgentDecisions(agentId, domain) {
43
- return Array.from(this.records.values()).filter((record) => record.agent_id === agentId && (domain ? record.domain === domain : true));
41
+ return Array.from(this.records.values()).filter((r) => r.agent_id === agentId && (domain ? r.domain === domain : true));
44
42
  }
45
43
  async getCompetencyProfile(agentId) {
46
44
  const records = await this.getAgentDecisions(agentId);
47
- const domainCounts = new Map();
48
- for (const record of records) {
49
- const count = domainCounts.get(record.domain) ?? 0;
50
- domainCounts.set(record.domain, count + 1);
51
- }
52
- const domains = Array.from(domainCounts.keys());
53
- const strengths = [];
54
- const weaknesses = [];
55
- for (const [domain, count] of domainCounts.entries()) {
56
- if (count >= 5) {
57
- strengths.push(domain);
58
- }
45
+ const stats = new Map();
46
+ for (const r of records) {
47
+ const cur = stats.get(r.domain) ?? { count: 0, conf: 0 };
48
+ cur.count += 1;
49
+ cur.conf += ("confidence" in r ? r.confidence : 0.8);
50
+ stats.set(r.domain, cur);
59
51
  }
60
- return {
61
- agent_id: agentId,
62
- domains,
63
- strengths,
64
- weaknesses,
65
- updated_at: new Date().toISOString()
66
- };
52
+ const domains = [...stats.keys()];
53
+ const strengths = domains.filter((d) => { const s = stats.get(d); return s.count >= 1 && (s.conf / s.count) >= 0.7; });
54
+ const weaknesses = domains.filter((d) => { const s = stats.get(d); return (s.conf / s.count) < 0.7; });
55
+ return { agent_id: agentId, domains, strengths, weaknesses, updated_at: new Date().toISOString() };
67
56
  }
68
57
  }
69
58
  exports.InMemoryStorage = InMemoryStorage;
@@ -1,34 +1,19 @@
1
- import type { CompetencyProfile, DecisionRecord, ConstraintSet } from '@learningnodes/elen-core';
1
+ import type { CompetencyProfile, ConstraintSet, DecisionContext, DecisionRecord, MinimalDecisionRecord } from '@learningnodes/elen-core';
2
2
  import type { SearchOptions } from '../types';
3
3
  import type { StorageAdapter } from './interface';
4
- export interface ProjectRecord {
5
- project_id: string;
6
- display_name: string;
7
- source_hint: string | null;
8
- created_at: string;
9
- }
10
- export interface ProjectSharingRecord {
11
- source_project_id: string;
12
- target_project_id: string;
13
- direction: 'one-way' | 'bi-directional';
14
- enabled: number;
15
- }
16
4
  export declare class SQLiteStorage implements StorageAdapter {
17
5
  private readonly db;
18
6
  private readonly projectId;
19
- constructor(path: string, projectId?: string);
7
+ private readonly defaultIsolation;
8
+ constructor(path: string, projectId?: string, defaultIsolation?: 'strict' | 'open');
20
9
  private init;
21
- private ensureProject;
22
- getProjects(): ProjectRecord[];
23
- getSharing(): ProjectSharingRecord[];
24
- upsertSharing(source: string, target: string, direction: 'one-way' | 'bi-directional', enabled: boolean): void;
25
- deleteSharing(source: string, target: string): void;
26
- private getAccessibleProjects;
10
+ saveDecision(decision: DecisionContext): Promise<void>;
27
11
  saveConstraintSet(constraintSet: ConstraintSet): Promise<void>;
28
12
  getConstraintSet(id: string): Promise<ConstraintSet | null>;
29
- saveRecord(record: DecisionRecord): Promise<void>;
30
- getRecord(recordId: string): Promise<DecisionRecord | null>;
31
- searchRecords(opts: SearchOptions): Promise<DecisionRecord[]>;
32
- getAgentDecisions(agentId: string, domain?: string): Promise<DecisionRecord[]>;
13
+ saveRecord(record: MinimalDecisionRecord | DecisionRecord): Promise<void>;
14
+ saveLegacyRecord(record: DecisionRecord): Promise<void>;
15
+ getRecord(recordId: string): Promise<MinimalDecisionRecord | DecisionRecord | null>;
16
+ searchRecords(opts: SearchOptions): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
17
+ getAgentDecisions(agentId: string, domain?: string): Promise<Array<MinimalDecisionRecord | DecisionRecord>>;
33
18
  getCompetencyProfile(agentId: string): Promise<CompetencyProfile>;
34
19
  }