mindgraph 0.15.1 → 0.16.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 CHANGED
@@ -21,16 +21,23 @@ const graph = new MindGraph({
21
21
  apiKey: "mg_...",
22
22
  });
23
23
 
24
- // Add a node
25
- const node = await graph.addNode({
26
- label: "User prefers dark mode",
27
- node_type: "Preference",
24
+ // Remember something — searchable the moment this resolves
25
+ const memory = await graph.remember("User prefers dark mode in every editor", {
26
+ custom_id: "pref:theme", // re-sending this key updates the same memory
28
27
  });
29
28
 
30
- // Search
29
+ // Search (BM25 + vector)
31
30
  const results = await graph.search("what does the user prefer?");
32
31
 
33
- // Connect knowledge
32
+ // Forget it later — preview first, then tombstone (reversible)
33
+ await graph.forget({ custom_id: "pref:theme" }, { dry_run: true });
34
+ await graph.forget({ custom_id: "pref:theme" });
35
+
36
+ // Or build structure explicitly
37
+ const node = await graph.addNode({
38
+ label: "User prefers dark mode",
39
+ node_type: "Preference",
40
+ });
34
41
  await graph.addLink({
35
42
  from_uid: node.uid,
36
43
  to_uid: "user_abc",
@@ -43,7 +50,7 @@ await graph.addLink({
43
50
  ### Constructor
44
51
 
45
52
  ```typescript
46
- new MindGraph({ baseUrl: string, apiKey?: string, jwt?: string })
53
+ new MindGraph({ baseUrl: string, apiKey?: string, jwt?: string, timeoutMs?: number })
47
54
  ```
48
55
 
49
56
  ### Reality Layer
@@ -107,6 +114,9 @@ const entity = await graph.findOrCreateEntity("Some Entity");
107
114
  |--------|-------------|
108
115
  | `session(req)` | Open a session, record traces, or close a session |
109
116
  | `journal(label, props, options?)` | Record a journal entry linked to an optional session |
117
+ | `remember(text, options?)` | Small-text fast path: synchronous write, BM25- and vector-searchable on return; `custom_id` makes re-sends an upsert |
118
+ | `forget(target, options?)` | Reversible removal by `{ uid }` or `{ custom_id }`; `dry_run` previews the affected edge uids |
119
+ | `setRememberInstructions(text, options?)` / `getRememberInstructions()` | Per-Space guidance for what agents should remember; surfaced on `remember()` and `retrieveContext()` |
110
120
  | `distill(req)` | Create a Summary, Lesson, or governed Skill candidate with source provenance |
111
121
  | `memoryConfig(req)` | Set/get preferences and memory policies |
112
122
 
@@ -257,3 +267,21 @@ try {
257
267
  ## License
258
268
 
259
269
  MIT
270
+
271
+ ### Retry and engine-error contract
272
+
273
+ HTTP 503 retries are bounded and apply only to reviewed reads (including selected
274
+ POST query actions) and keyed `/agent/plan` work operations: `claim_task`,
275
+ `heartbeat`, `start_iteration`, `checkpoint_iteration`, `block_task`,
276
+ `complete_task`, and `abandon_iteration`. These work operations atomically store
277
+ their mutation and idempotency receipt. A nonempty `idempotency_key` must stay
278
+ unchanged across attempts. Other writes are not retried automatically; supplying
279
+ a key or telemetry request ID on an unsupported route does not change that.
280
+
281
+ `MindGraphError` preserves the response `status` and `body` and exposes optional
282
+ `code` and `retriable` fields. Explicit `retriable: false` always stops retries,
283
+ including admission failures and index maintenance. Memory limits, timeouts,
284
+ and cancellation remain failures; they do not become empty search results.
285
+ Older responses without retry guidance retain retries only for the reviewed
286
+ operations above. Each delay, including fallback backoff, is capped at 10 seconds.
287
+ Network exceptions and other HTTP statuses are not automatically retried.
package/dist/index.d.mts CHANGED
@@ -114,7 +114,10 @@ interface EnrichedSearchResponse {
114
114
  interface PathStep {
115
115
  node_uid: string;
116
116
  label: string;
117
+ /** Plain semantic node type; typed shortcuts unwrap `{Custom: "TYPE"}`. */
117
118
  node_type: string;
119
+ /** Plain semantic relation name. Typed traversal shortcuts normalize the
120
+ * server's externally tagged `{Custom: "NAME"}` wire form to this string. */
118
121
  edge_type: string | null;
119
122
  depth: number;
120
123
  parent_uid: string | null;
@@ -524,6 +527,13 @@ interface SessionRequest {
524
527
  model?: string;
525
528
  props?: Record<string, unknown>;
526
529
  agent_id?: string;
530
+ /**
531
+ * Server-side replay key (`/memory/session` supports it on every action):
532
+ * the same key with the same payload returns the stored receipt; the same
533
+ * key with a different payload is a 409. Use a host's operation id so a
534
+ * re-run compaction capture or trace is byte-identical.
535
+ */
536
+ idempotency_key?: string;
527
537
  }
528
538
  /** Caller-authored content for a governed skill candidate. */
529
539
  interface SkillDistillProps {
@@ -559,13 +569,92 @@ interface SkillDistillRequest extends DistillRequestBase {
559
569
  }
560
570
  type DistillRequest = SummaryOrLessonDistillRequest | SkillDistillRequest;
561
571
  interface MemoryConfigRequest {
562
- action: "set_preference" | "get_preferences" | "set_policy" | "get_policies";
572
+ action: "set_preference" | "get_preferences" | "set_policy" | "get_policies" | "set_remember_instructions" | "get_remember_instructions";
563
573
  label?: string;
564
574
  summary?: string;
565
575
  confidence?: number;
566
576
  salience?: number;
567
577
  props?: Record<string, unknown>;
568
578
  agent_id?: string;
579
+ /** Target Space for the `remember_instructions` actions (default: org space). */
580
+ space_uid?: string;
581
+ /** Instruction text for `set_remember_instructions`; empty clears it. */
582
+ text?: string;
583
+ }
584
+ interface RememberOptions {
585
+ /**
586
+ * Caller-owned idempotency and upsert key. Re-sending the same `custom_id`
587
+ * with new text edits the same node (version bump, history kept); with the
588
+ * same text it is a no-op.
589
+ */
590
+ custom_id?: string;
591
+ /** Node label; defaults to the first line of `text` (80 chars). */
592
+ label?: string;
593
+ props?: Record<string, unknown>;
594
+ confidence?: number;
595
+ salience?: number;
596
+ agent_id?: string;
597
+ /** Explicit target Space; default is the org space. */
598
+ space_uid?: string;
599
+ /**
600
+ * Without a `custom_id`, what to do on an exact write-time-resolution hit:
601
+ * `reuse` (default) returns the existing node, `create` inserts anyway.
602
+ */
603
+ on_near_duplicate?: "reuse" | "create";
604
+ }
605
+ interface RememberRequest extends RememberOptions {
606
+ text: string;
607
+ }
608
+ interface RememberResponse {
609
+ uid: string;
610
+ custom_id: string | null;
611
+ /** `inserted` (201) | `updated` | `unchanged` | `reused` (200). */
612
+ action: "inserted" | "updated" | "unchanged" | "reused";
613
+ label: string;
614
+ version: number;
615
+ space_uid: string;
616
+ /** True when the vector was stored before this response. */
617
+ embedded: boolean;
618
+ searchable: {
619
+ bm25: boolean;
620
+ vector: boolean;
621
+ };
622
+ near_duplicates: unknown[];
623
+ /** The Space's `remember_instructions`, if set. */
624
+ remember_instructions: string | null;
625
+ }
626
+ type ForgetTarget = {
627
+ uid: string;
628
+ custom_id?: never;
629
+ } | {
630
+ custom_id: string;
631
+ uid?: never;
632
+ };
633
+ interface ForgetOptions {
634
+ /** Preview the affected edges without changing anything. */
635
+ dry_run?: boolean;
636
+ /** Also tombstone connected edges (default true). */
637
+ cascade?: boolean;
638
+ reason?: string;
639
+ agent_id?: string;
640
+ }
641
+ type ForgetRequest = ForgetTarget & ForgetOptions;
642
+ interface ForgetResponse {
643
+ uid: string;
644
+ label: string;
645
+ dry_run: boolean;
646
+ cascade: boolean;
647
+ /** Present on a dry run. */
648
+ would_tombstone_edges?: number;
649
+ /** Present after execution. */
650
+ action?: "forgotten";
651
+ edges_tombstoned?: number;
652
+ /** Edge uids removed (or that would be); undo each with `/evolve restore_edge`. */
653
+ edge_uids: string[];
654
+ undo: {
655
+ node: string;
656
+ edges: string;
657
+ };
569
658
  }
570
659
  interface MemorySyncRequest {
571
660
  action: "scan" | "begin" | "record" | "finalize" | "status" | "abandon";
@@ -1193,10 +1282,18 @@ interface MindGraphConfig {
1193
1282
  * uses this value for authorization or result membership.
1194
1283
  */
1195
1284
  telemetrySurface?: "dashboard" | "mcp";
1196
- /** Max retries for 503 (server warming up) responses. Default: 3. Set to 0 to disable. */
1285
+ /** Max 503 retries for reviewed reads or keyed work operations. retriable:false stops retries. Default: 3. Set to 0 to disable. */
1197
1286
  maxRetries?: number;
1198
- /** Initial backoff in ms before first retry. Doubles each attempt. Default: 1000. */
1287
+ /** Initial backoff in ms before first retry. Doubles each attempt, capped at 10 seconds. Default: 1000. */
1199
1288
  retryBackoffMs?: number;
1289
+ /**
1290
+ * Per-request deadline in ms. When set, each HTTP attempt is aborted after
1291
+ * this long and the call rejects with a `MindGraphError` whose `code` is
1292
+ * `"timeout"` (`status` 0, `retriable: false`). Unset (default) keeps the
1293
+ * historical behaviour: no deadline and no AbortSignal on the request.
1294
+ * Fail-open wrappers (agent memory providers) set this to a few seconds.
1295
+ */
1296
+ timeoutMs?: number;
1200
1297
  }
1201
1298
  /** Schema-level behavior for compiled semantic-match diagnostics. */
1202
1299
  type SemanticValidationMode = "off" | "advisory" | "review" | "strict";
@@ -1829,6 +1926,8 @@ type DomainObject = GraphNode;
1829
1926
  declare class MindGraphError extends Error {
1830
1927
  status: number;
1831
1928
  body?: unknown | undefined;
1929
+ readonly code?: string;
1930
+ readonly retriable?: boolean;
1832
1931
  constructor(message: string, status: number, body?: unknown | undefined);
1833
1932
  }
1834
1933
  declare class MindGraph {
@@ -1836,9 +1935,17 @@ declare class MindGraph {
1836
1935
  private headers;
1837
1936
  private maxRetries;
1838
1937
  private retryBackoffMs;
1938
+ private timeoutMs?;
1839
1939
  private telemetrySurface?;
1840
1940
  constructor(config: MindGraphConfig);
1841
1941
  private request;
1942
+ /**
1943
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
1944
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
1945
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
1946
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
1947
+ */
1948
+ private fetchWithDeadline;
1842
1949
  private get;
1843
1950
  private post;
1844
1951
  private patch;
@@ -1991,6 +2098,35 @@ declare class MindGraph {
1991
2098
  distill(req: DistillRequest): Promise<unknown>;
1992
2099
  memoryConfig(req: MemoryConfigRequest): Promise<unknown>;
1993
2100
  memorySync(req: MemorySyncRequest): Promise<unknown>;
2101
+ /**
2102
+ * Store a small piece of text as a memory, synchronously. The node is
2103
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
2104
+ * Pass `custom_id` to make re-sends an upsert of the same node.
2105
+ */
2106
+ remember(text: string, options?: RememberOptions): Promise<RememberResponse>;
2107
+ /**
2108
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
2109
+ * connected edges). `dry_run: true` previews the affected edge uids first.
2110
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
2111
+ */
2112
+ forget(target: ForgetTarget, options?: ForgetOptions): Promise<ForgetResponse>;
2113
+ /**
2114
+ * Operator guidance for what agents should remember into a Space; surfaced
2115
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
2116
+ */
2117
+ setRememberInstructions(text: string, options?: {
2118
+ space_uid?: string;
2119
+ agent_id?: string;
2120
+ }): Promise<{
2121
+ space_uid: string;
2122
+ remember_instructions: string | null;
2123
+ }>;
2124
+ getRememberInstructions(options?: {
2125
+ space_uid?: string;
2126
+ }): Promise<{
2127
+ space_uid: string;
2128
+ remember_instructions: string | null;
2129
+ }>;
1994
2130
  plan(req: PlanRequest): Promise<unknown>;
1995
2131
  governance(req: GovernanceRequest): Promise<unknown>;
1996
2132
  execution(req: ExecutionRequest): Promise<unknown>;
@@ -2455,4 +2591,4 @@ declare class MindGraph {
2455
2591
  }>;
2456
2592
  }
2457
2593
 
2458
- export { type AggregatePoint, type AnalogyCandidate, type AppendSeriesResponse, type ArgumentRequest, type ArticleResult, type BatchAggregateSeriesResponse, type BatchLatestSeriesResponse, type BatchRequest, type BelievedBy, type CanonicalAttachmentAuthority, type CanonicalAttachmentBinding, type CanonicalAttachmentDirection, type CaptureRequest, type ClaimHub, type ClearResponse, type ClusterClaimSample, type ClusteredClaimHub, type CommitmentRequest, type ConceptCluster, type CreateDomainObjectRequest, type CreateOntologySchemaRequest, type CreateSeriesResponse, type DecayRequest, type DeleteSeriesResponse, type DeliberationRequest, type DialecticalPair, type DistillRequest, type DocumentRef, type DomainObject, type DomainObjectMutationResponse, type EnrichedSearchResponse, type EntityBridge, type EntityRequest, type EpistemicCounts, type EventProps, type EvolveRequest, type ExecutionRequest, type FieldDefinition, type FieldSemantic, type FieldSemanticKind, type FiscalYearEnd, type GovernanceRequest, type GovernanceTarget, type GraphEdge, type GraphNode, type IngestChunkRequest, type IngestChunkResponse, type IngestDocumentRequest, type IngestDocumentResponse, type IngestSessionRequest, type InquiryRequest, type Job, type ListArticlesResponse, type ListSeriesResponse, type MeasurementPoint, type MemoryConfigRequest, type MemorySyncRequest, MindGraph, type MindGraphConfig, MindGraphError, type NationProps, type NormativeKind, type ObjectBacking, type OntologicalBaseKind, type OntologyDuplicateAudit, type OntologyObjectType, type OntologyObjectTypeInput, type OntologyPredicateOperator, type OntologyProposal, type OntologyQueryPredicate, type OntologyRelationType, type OntologyRelationTypeInput, type OntologySchema, type OntologySchemaDetail, type OntologySeriesBinding, type OntologyToolDescriptor, type OrganizationProps, type Participant, type ParticipantSlotKind, type PathStep, type PersonProps, type PlaceProps, type PlanRequest, type ProcedureRequest, type ProposalEdits, type PurgeRequest, type RankedClaimHub, type RelatedDomainObjectsRequest, type RelationBacking, type RetrieveContextRequest, type RetrieveContextResponse, type RetrieveRequest, type RiskRequest, type SearchChunk, type SearchResult, type SemanticDependence, type SemanticEvidenceRef, type SemanticEvidenceSourceKind, type SemanticGeneratorRef, type SemanticIdentityMode, type SemanticMatch, type SemanticMatchAlternative, type SemanticMatchStatus, type SemanticModifier, type SemanticModifierMatch, type SemanticNegativeControl, type SemanticParticipantSlot, type SemanticRelationKind, type SemanticReviewDecision, type SemanticRigidity, type SemanticRuntimeTemplate, type SemanticSignal, type SemanticSignalKind, type SemanticTemplateRef, type SemanticTemporalMode, type SemanticTenantBindings, type SemanticValidationAuthoringMode, type SemanticValidationMode, type SeriesAggregateResponse, type SeriesBacking, type SeriesContextAnnotation, type SeriesLatestResponse, type SeriesNode, type SeriesProps, type SeriesRequest, type SeriesWindowResponse, type SessionRequest, type SignalsQuery, type SignalsResponse, type SkillDistillProps, type SkillDistillRequest, type SourceChunk, type StructureRequest, type StructuredOntologyQueryRequest, type StructuredOntologyQueryResponse, type SynthesisJobResponse, type TextSelector, type TheorySupportGap, type TraverseRequest, type UpdateDomainObjectRequest, type UpdateOntologySchemaRequest };
2594
+ export { type AggregatePoint, type AnalogyCandidate, type AppendSeriesResponse, type ArgumentRequest, type ArticleResult, type BatchAggregateSeriesResponse, type BatchLatestSeriesResponse, type BatchRequest, type BelievedBy, type CanonicalAttachmentAuthority, type CanonicalAttachmentBinding, type CanonicalAttachmentDirection, type CaptureRequest, type ClaimHub, type ClearResponse, type ClusterClaimSample, type ClusteredClaimHub, type CommitmentRequest, type ConceptCluster, type CreateDomainObjectRequest, type CreateOntologySchemaRequest, type CreateSeriesResponse, type DecayRequest, type DeleteSeriesResponse, type DeliberationRequest, type DialecticalPair, type DistillRequest, type DocumentRef, type DomainObject, type DomainObjectMutationResponse, type EnrichedSearchResponse, type EntityBridge, type EntityRequest, type EpistemicCounts, type EventProps, type EvolveRequest, type ExecutionRequest, type FieldDefinition, type FieldSemantic, type FieldSemanticKind, type FiscalYearEnd, type ForgetOptions, type ForgetRequest, type ForgetResponse, type ForgetTarget, type GovernanceRequest, type GovernanceTarget, type GraphEdge, type GraphNode, type IngestChunkRequest, type IngestChunkResponse, type IngestDocumentRequest, type IngestDocumentResponse, type IngestSessionRequest, type InquiryRequest, type Job, type ListArticlesResponse, type ListSeriesResponse, type MeasurementPoint, type MemoryConfigRequest, type MemorySyncRequest, MindGraph, type MindGraphConfig, MindGraphError, type NationProps, type NormativeKind, type ObjectBacking, type OntologicalBaseKind, type OntologyDuplicateAudit, type OntologyObjectType, type OntologyObjectTypeInput, type OntologyPredicateOperator, type OntologyProposal, type OntologyQueryPredicate, type OntologyRelationType, type OntologyRelationTypeInput, type OntologySchema, type OntologySchemaDetail, type OntologySeriesBinding, type OntologyToolDescriptor, type OrganizationProps, type Participant, type ParticipantSlotKind, type PathStep, type PersonProps, type PlaceProps, type PlanRequest, type ProcedureRequest, type ProposalEdits, type PurgeRequest, type RankedClaimHub, type RelatedDomainObjectsRequest, type RelationBacking, type RememberOptions, type RememberRequest, type RememberResponse, type RetrieveContextRequest, type RetrieveContextResponse, type RetrieveRequest, type RiskRequest, type SearchChunk, type SearchResult, type SemanticDependence, type SemanticEvidenceRef, type SemanticEvidenceSourceKind, type SemanticGeneratorRef, type SemanticIdentityMode, type SemanticMatch, type SemanticMatchAlternative, type SemanticMatchStatus, type SemanticModifier, type SemanticModifierMatch, type SemanticNegativeControl, type SemanticParticipantSlot, type SemanticRelationKind, type SemanticReviewDecision, type SemanticRigidity, type SemanticRuntimeTemplate, type SemanticSignal, type SemanticSignalKind, type SemanticTemplateRef, type SemanticTemporalMode, type SemanticTenantBindings, type SemanticValidationAuthoringMode, type SemanticValidationMode, type SeriesAggregateResponse, type SeriesBacking, type SeriesContextAnnotation, type SeriesLatestResponse, type SeriesNode, type SeriesProps, type SeriesRequest, type SeriesWindowResponse, type SessionRequest, type SignalsQuery, type SignalsResponse, type SkillDistillProps, type SkillDistillRequest, type SourceChunk, type StructureRequest, type StructuredOntologyQueryRequest, type StructuredOntologyQueryResponse, type SynthesisJobResponse, type TextSelector, type TheorySupportGap, type TraverseRequest, type UpdateDomainObjectRequest, type UpdateOntologySchemaRequest };
package/dist/index.d.ts CHANGED
@@ -114,7 +114,10 @@ interface EnrichedSearchResponse {
114
114
  interface PathStep {
115
115
  node_uid: string;
116
116
  label: string;
117
+ /** Plain semantic node type; typed shortcuts unwrap `{Custom: "TYPE"}`. */
117
118
  node_type: string;
119
+ /** Plain semantic relation name. Typed traversal shortcuts normalize the
120
+ * server's externally tagged `{Custom: "NAME"}` wire form to this string. */
118
121
  edge_type: string | null;
119
122
  depth: number;
120
123
  parent_uid: string | null;
@@ -524,6 +527,13 @@ interface SessionRequest {
524
527
  model?: string;
525
528
  props?: Record<string, unknown>;
526
529
  agent_id?: string;
530
+ /**
531
+ * Server-side replay key (`/memory/session` supports it on every action):
532
+ * the same key with the same payload returns the stored receipt; the same
533
+ * key with a different payload is a 409. Use a host's operation id so a
534
+ * re-run compaction capture or trace is byte-identical.
535
+ */
536
+ idempotency_key?: string;
527
537
  }
528
538
  /** Caller-authored content for a governed skill candidate. */
529
539
  interface SkillDistillProps {
@@ -559,13 +569,92 @@ interface SkillDistillRequest extends DistillRequestBase {
559
569
  }
560
570
  type DistillRequest = SummaryOrLessonDistillRequest | SkillDistillRequest;
561
571
  interface MemoryConfigRequest {
562
- action: "set_preference" | "get_preferences" | "set_policy" | "get_policies";
572
+ action: "set_preference" | "get_preferences" | "set_policy" | "get_policies" | "set_remember_instructions" | "get_remember_instructions";
563
573
  label?: string;
564
574
  summary?: string;
565
575
  confidence?: number;
566
576
  salience?: number;
567
577
  props?: Record<string, unknown>;
568
578
  agent_id?: string;
579
+ /** Target Space for the `remember_instructions` actions (default: org space). */
580
+ space_uid?: string;
581
+ /** Instruction text for `set_remember_instructions`; empty clears it. */
582
+ text?: string;
583
+ }
584
+ interface RememberOptions {
585
+ /**
586
+ * Caller-owned idempotency and upsert key. Re-sending the same `custom_id`
587
+ * with new text edits the same node (version bump, history kept); with the
588
+ * same text it is a no-op.
589
+ */
590
+ custom_id?: string;
591
+ /** Node label; defaults to the first line of `text` (80 chars). */
592
+ label?: string;
593
+ props?: Record<string, unknown>;
594
+ confidence?: number;
595
+ salience?: number;
596
+ agent_id?: string;
597
+ /** Explicit target Space; default is the org space. */
598
+ space_uid?: string;
599
+ /**
600
+ * Without a `custom_id`, what to do on an exact write-time-resolution hit:
601
+ * `reuse` (default) returns the existing node, `create` inserts anyway.
602
+ */
603
+ on_near_duplicate?: "reuse" | "create";
604
+ }
605
+ interface RememberRequest extends RememberOptions {
606
+ text: string;
607
+ }
608
+ interface RememberResponse {
609
+ uid: string;
610
+ custom_id: string | null;
611
+ /** `inserted` (201) | `updated` | `unchanged` | `reused` (200). */
612
+ action: "inserted" | "updated" | "unchanged" | "reused";
613
+ label: string;
614
+ version: number;
615
+ space_uid: string;
616
+ /** True when the vector was stored before this response. */
617
+ embedded: boolean;
618
+ searchable: {
619
+ bm25: boolean;
620
+ vector: boolean;
621
+ };
622
+ near_duplicates: unknown[];
623
+ /** The Space's `remember_instructions`, if set. */
624
+ remember_instructions: string | null;
625
+ }
626
+ type ForgetTarget = {
627
+ uid: string;
628
+ custom_id?: never;
629
+ } | {
630
+ custom_id: string;
631
+ uid?: never;
632
+ };
633
+ interface ForgetOptions {
634
+ /** Preview the affected edges without changing anything. */
635
+ dry_run?: boolean;
636
+ /** Also tombstone connected edges (default true). */
637
+ cascade?: boolean;
638
+ reason?: string;
639
+ agent_id?: string;
640
+ }
641
+ type ForgetRequest = ForgetTarget & ForgetOptions;
642
+ interface ForgetResponse {
643
+ uid: string;
644
+ label: string;
645
+ dry_run: boolean;
646
+ cascade: boolean;
647
+ /** Present on a dry run. */
648
+ would_tombstone_edges?: number;
649
+ /** Present after execution. */
650
+ action?: "forgotten";
651
+ edges_tombstoned?: number;
652
+ /** Edge uids removed (or that would be); undo each with `/evolve restore_edge`. */
653
+ edge_uids: string[];
654
+ undo: {
655
+ node: string;
656
+ edges: string;
657
+ };
569
658
  }
570
659
  interface MemorySyncRequest {
571
660
  action: "scan" | "begin" | "record" | "finalize" | "status" | "abandon";
@@ -1193,10 +1282,18 @@ interface MindGraphConfig {
1193
1282
  * uses this value for authorization or result membership.
1194
1283
  */
1195
1284
  telemetrySurface?: "dashboard" | "mcp";
1196
- /** Max retries for 503 (server warming up) responses. Default: 3. Set to 0 to disable. */
1285
+ /** Max 503 retries for reviewed reads or keyed work operations. retriable:false stops retries. Default: 3. Set to 0 to disable. */
1197
1286
  maxRetries?: number;
1198
- /** Initial backoff in ms before first retry. Doubles each attempt. Default: 1000. */
1287
+ /** Initial backoff in ms before first retry. Doubles each attempt, capped at 10 seconds. Default: 1000. */
1199
1288
  retryBackoffMs?: number;
1289
+ /**
1290
+ * Per-request deadline in ms. When set, each HTTP attempt is aborted after
1291
+ * this long and the call rejects with a `MindGraphError` whose `code` is
1292
+ * `"timeout"` (`status` 0, `retriable: false`). Unset (default) keeps the
1293
+ * historical behaviour: no deadline and no AbortSignal on the request.
1294
+ * Fail-open wrappers (agent memory providers) set this to a few seconds.
1295
+ */
1296
+ timeoutMs?: number;
1200
1297
  }
1201
1298
  /** Schema-level behavior for compiled semantic-match diagnostics. */
1202
1299
  type SemanticValidationMode = "off" | "advisory" | "review" | "strict";
@@ -1829,6 +1926,8 @@ type DomainObject = GraphNode;
1829
1926
  declare class MindGraphError extends Error {
1830
1927
  status: number;
1831
1928
  body?: unknown | undefined;
1929
+ readonly code?: string;
1930
+ readonly retriable?: boolean;
1832
1931
  constructor(message: string, status: number, body?: unknown | undefined);
1833
1932
  }
1834
1933
  declare class MindGraph {
@@ -1836,9 +1935,17 @@ declare class MindGraph {
1836
1935
  private headers;
1837
1936
  private maxRetries;
1838
1937
  private retryBackoffMs;
1938
+ private timeoutMs?;
1839
1939
  private telemetrySurface?;
1840
1940
  constructor(config: MindGraphConfig);
1841
1941
  private request;
1942
+ /**
1943
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
1944
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
1945
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
1946
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
1947
+ */
1948
+ private fetchWithDeadline;
1842
1949
  private get;
1843
1950
  private post;
1844
1951
  private patch;
@@ -1991,6 +2098,35 @@ declare class MindGraph {
1991
2098
  distill(req: DistillRequest): Promise<unknown>;
1992
2099
  memoryConfig(req: MemoryConfigRequest): Promise<unknown>;
1993
2100
  memorySync(req: MemorySyncRequest): Promise<unknown>;
2101
+ /**
2102
+ * Store a small piece of text as a memory, synchronously. The node is
2103
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
2104
+ * Pass `custom_id` to make re-sends an upsert of the same node.
2105
+ */
2106
+ remember(text: string, options?: RememberOptions): Promise<RememberResponse>;
2107
+ /**
2108
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
2109
+ * connected edges). `dry_run: true` previews the affected edge uids first.
2110
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
2111
+ */
2112
+ forget(target: ForgetTarget, options?: ForgetOptions): Promise<ForgetResponse>;
2113
+ /**
2114
+ * Operator guidance for what agents should remember into a Space; surfaced
2115
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
2116
+ */
2117
+ setRememberInstructions(text: string, options?: {
2118
+ space_uid?: string;
2119
+ agent_id?: string;
2120
+ }): Promise<{
2121
+ space_uid: string;
2122
+ remember_instructions: string | null;
2123
+ }>;
2124
+ getRememberInstructions(options?: {
2125
+ space_uid?: string;
2126
+ }): Promise<{
2127
+ space_uid: string;
2128
+ remember_instructions: string | null;
2129
+ }>;
1994
2130
  plan(req: PlanRequest): Promise<unknown>;
1995
2131
  governance(req: GovernanceRequest): Promise<unknown>;
1996
2132
  execution(req: ExecutionRequest): Promise<unknown>;
@@ -2455,4 +2591,4 @@ declare class MindGraph {
2455
2591
  }>;
2456
2592
  }
2457
2593
 
2458
- export { type AggregatePoint, type AnalogyCandidate, type AppendSeriesResponse, type ArgumentRequest, type ArticleResult, type BatchAggregateSeriesResponse, type BatchLatestSeriesResponse, type BatchRequest, type BelievedBy, type CanonicalAttachmentAuthority, type CanonicalAttachmentBinding, type CanonicalAttachmentDirection, type CaptureRequest, type ClaimHub, type ClearResponse, type ClusterClaimSample, type ClusteredClaimHub, type CommitmentRequest, type ConceptCluster, type CreateDomainObjectRequest, type CreateOntologySchemaRequest, type CreateSeriesResponse, type DecayRequest, type DeleteSeriesResponse, type DeliberationRequest, type DialecticalPair, type DistillRequest, type DocumentRef, type DomainObject, type DomainObjectMutationResponse, type EnrichedSearchResponse, type EntityBridge, type EntityRequest, type EpistemicCounts, type EventProps, type EvolveRequest, type ExecutionRequest, type FieldDefinition, type FieldSemantic, type FieldSemanticKind, type FiscalYearEnd, type GovernanceRequest, type GovernanceTarget, type GraphEdge, type GraphNode, type IngestChunkRequest, type IngestChunkResponse, type IngestDocumentRequest, type IngestDocumentResponse, type IngestSessionRequest, type InquiryRequest, type Job, type ListArticlesResponse, type ListSeriesResponse, type MeasurementPoint, type MemoryConfigRequest, type MemorySyncRequest, MindGraph, type MindGraphConfig, MindGraphError, type NationProps, type NormativeKind, type ObjectBacking, type OntologicalBaseKind, type OntologyDuplicateAudit, type OntologyObjectType, type OntologyObjectTypeInput, type OntologyPredicateOperator, type OntologyProposal, type OntologyQueryPredicate, type OntologyRelationType, type OntologyRelationTypeInput, type OntologySchema, type OntologySchemaDetail, type OntologySeriesBinding, type OntologyToolDescriptor, type OrganizationProps, type Participant, type ParticipantSlotKind, type PathStep, type PersonProps, type PlaceProps, type PlanRequest, type ProcedureRequest, type ProposalEdits, type PurgeRequest, type RankedClaimHub, type RelatedDomainObjectsRequest, type RelationBacking, type RetrieveContextRequest, type RetrieveContextResponse, type RetrieveRequest, type RiskRequest, type SearchChunk, type SearchResult, type SemanticDependence, type SemanticEvidenceRef, type SemanticEvidenceSourceKind, type SemanticGeneratorRef, type SemanticIdentityMode, type SemanticMatch, type SemanticMatchAlternative, type SemanticMatchStatus, type SemanticModifier, type SemanticModifierMatch, type SemanticNegativeControl, type SemanticParticipantSlot, type SemanticRelationKind, type SemanticReviewDecision, type SemanticRigidity, type SemanticRuntimeTemplate, type SemanticSignal, type SemanticSignalKind, type SemanticTemplateRef, type SemanticTemporalMode, type SemanticTenantBindings, type SemanticValidationAuthoringMode, type SemanticValidationMode, type SeriesAggregateResponse, type SeriesBacking, type SeriesContextAnnotation, type SeriesLatestResponse, type SeriesNode, type SeriesProps, type SeriesRequest, type SeriesWindowResponse, type SessionRequest, type SignalsQuery, type SignalsResponse, type SkillDistillProps, type SkillDistillRequest, type SourceChunk, type StructureRequest, type StructuredOntologyQueryRequest, type StructuredOntologyQueryResponse, type SynthesisJobResponse, type TextSelector, type TheorySupportGap, type TraverseRequest, type UpdateDomainObjectRequest, type UpdateOntologySchemaRequest };
2594
+ export { type AggregatePoint, type AnalogyCandidate, type AppendSeriesResponse, type ArgumentRequest, type ArticleResult, type BatchAggregateSeriesResponse, type BatchLatestSeriesResponse, type BatchRequest, type BelievedBy, type CanonicalAttachmentAuthority, type CanonicalAttachmentBinding, type CanonicalAttachmentDirection, type CaptureRequest, type ClaimHub, type ClearResponse, type ClusterClaimSample, type ClusteredClaimHub, type CommitmentRequest, type ConceptCluster, type CreateDomainObjectRequest, type CreateOntologySchemaRequest, type CreateSeriesResponse, type DecayRequest, type DeleteSeriesResponse, type DeliberationRequest, type DialecticalPair, type DistillRequest, type DocumentRef, type DomainObject, type DomainObjectMutationResponse, type EnrichedSearchResponse, type EntityBridge, type EntityRequest, type EpistemicCounts, type EventProps, type EvolveRequest, type ExecutionRequest, type FieldDefinition, type FieldSemantic, type FieldSemanticKind, type FiscalYearEnd, type ForgetOptions, type ForgetRequest, type ForgetResponse, type ForgetTarget, type GovernanceRequest, type GovernanceTarget, type GraphEdge, type GraphNode, type IngestChunkRequest, type IngestChunkResponse, type IngestDocumentRequest, type IngestDocumentResponse, type IngestSessionRequest, type InquiryRequest, type Job, type ListArticlesResponse, type ListSeriesResponse, type MeasurementPoint, type MemoryConfigRequest, type MemorySyncRequest, MindGraph, type MindGraphConfig, MindGraphError, type NationProps, type NormativeKind, type ObjectBacking, type OntologicalBaseKind, type OntologyDuplicateAudit, type OntologyObjectType, type OntologyObjectTypeInput, type OntologyPredicateOperator, type OntologyProposal, type OntologyQueryPredicate, type OntologyRelationType, type OntologyRelationTypeInput, type OntologySchema, type OntologySchemaDetail, type OntologySeriesBinding, type OntologyToolDescriptor, type OrganizationProps, type Participant, type ParticipantSlotKind, type PathStep, type PersonProps, type PlaceProps, type PlanRequest, type ProcedureRequest, type ProposalEdits, type PurgeRequest, type RankedClaimHub, type RelatedDomainObjectsRequest, type RelationBacking, type RememberOptions, type RememberRequest, type RememberResponse, type RetrieveContextRequest, type RetrieveContextResponse, type RetrieveRequest, type RiskRequest, type SearchChunk, type SearchResult, type SemanticDependence, type SemanticEvidenceRef, type SemanticEvidenceSourceKind, type SemanticGeneratorRef, type SemanticIdentityMode, type SemanticMatch, type SemanticMatchAlternative, type SemanticMatchStatus, type SemanticModifier, type SemanticModifierMatch, type SemanticNegativeControl, type SemanticParticipantSlot, type SemanticRelationKind, type SemanticReviewDecision, type SemanticRigidity, type SemanticRuntimeTemplate, type SemanticSignal, type SemanticSignalKind, type SemanticTemplateRef, type SemanticTemporalMode, type SemanticTenantBindings, type SemanticValidationAuthoringMode, type SemanticValidationMode, type SeriesAggregateResponse, type SeriesBacking, type SeriesContextAnnotation, type SeriesLatestResponse, type SeriesNode, type SeriesProps, type SeriesRequest, type SeriesWindowResponse, type SessionRequest, type SignalsQuery, type SignalsResponse, type SkillDistillProps, type SkillDistillRequest, type SourceChunk, type StructureRequest, type StructuredOntologyQueryRequest, type StructuredOntologyQueryResponse, type SynthesisJobResponse, type TextSelector, type TheorySupportGap, type TraverseRequest, type UpdateDomainObjectRequest, type UpdateOntologySchemaRequest };
package/dist/index.js CHANGED
@@ -25,16 +25,120 @@ __export(index_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
+ // src/retry.ts
29
+ var READ_POSTS = /* @__PURE__ */ new Set([
30
+ "/search",
31
+ "/retrieve/context",
32
+ "/nodes/batch",
33
+ "/edges/batch",
34
+ "/subgraph",
35
+ "/embeddings/search",
36
+ "/embeddings/search-text",
37
+ "/ontology/query",
38
+ "/ontology/query/structured",
39
+ "/ontology/objects/search"
40
+ ]);
41
+ var READ_ACTIONS = {
42
+ "/retrieve": [
43
+ "text",
44
+ "semantic",
45
+ "hybrid",
46
+ "active_goals",
47
+ "open_questions",
48
+ "preferences",
49
+ "weak_claims",
50
+ "pending_approvals",
51
+ "unresolved_contradictions",
52
+ "merge_candidates",
53
+ "stale_derivations",
54
+ "curation_counts",
55
+ "layer",
56
+ "recent"
57
+ ],
58
+ "/traverse": ["chain", "top_k_paths", "neighborhood", "path", "subgraph"],
59
+ "/reality/series": ["window", "aggregate", "latest", "list_for_entity", "batch_latest", "batch_aggregate"],
60
+ "/intent/deliberation": ["get_open"],
61
+ "/action/risk": ["get_assessments"],
62
+ "/action/skill": ["get", "list"],
63
+ "/memory/config": ["get_preferences", "get_policies"],
64
+ "/memory/sync": ["status"],
65
+ "/agent/plan": ["get_plan", "resume_work"],
66
+ "/agent/governance": ["get_pending", "check"],
67
+ "/agent/execution": ["get_executions"],
68
+ "/evolve": ["history", "snapshot"]
69
+ };
70
+ var IDEMPOTENT_WORK = /* @__PURE__ */ new Set([
71
+ "claim_task",
72
+ "heartbeat",
73
+ "start_iteration",
74
+ "checkpoint_iteration",
75
+ "block_task",
76
+ "complete_task",
77
+ "abandon_iteration"
78
+ ]);
79
+ function errorFields(body) {
80
+ if (!body || typeof body !== "object" || Array.isArray(body)) return {};
81
+ const fields = body;
82
+ return {
83
+ ...typeof fields.code === "string" ? { code: fields.code } : {},
84
+ ...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
85
+ };
86
+ }
87
+ function canRetryRequest(method, path, body) {
88
+ if (method === "GET" || method === "HEAD") return true;
89
+ if (method !== "POST") return false;
90
+ if (READ_POSTS.has(path)) return true;
91
+ const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
92
+ if (typeof fields.action !== "string") return false;
93
+ if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path) && READ_ACTIONS[path].includes(fields.action)) return true;
94
+ return path === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
95
+ }
96
+ function permitsRetry(fields) {
97
+ return fields.retriable !== false && ![
98
+ "query_memory_budget_exceeded",
99
+ "query_timeout",
100
+ "query_cancelled",
101
+ "vector_index_rebuilding"
102
+ ].includes(fields.code ?? "");
103
+ }
104
+ function retryDelayMs(header, backoff, attempt) {
105
+ const hint = Number(header);
106
+ return Number.isFinite(hint) && hint > 0 ? Math.min(hint * 1e3, 1e4) : Math.min(backoff * 2 ** Math.min(attempt, 32), 1e4);
107
+ }
108
+
28
109
  // src/client.ts
29
110
  function seg(value) {
30
111
  return encodeURIComponent(String(value));
31
112
  }
113
+ function pathSteps(response) {
114
+ const wrapped = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
115
+ const raw = wrapped?.steps ?? response;
116
+ if (!Array.isArray(raw)) return [];
117
+ return raw.flatMap((value) => {
118
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
119
+ const step = value;
120
+ const taggedName = (tagged) => {
121
+ if (typeof tagged === "string") return tagged;
122
+ if (!tagged || typeof tagged !== "object" || Array.isArray(tagged)) return null;
123
+ const custom = tagged.Custom;
124
+ return typeof custom === "string" ? custom : null;
125
+ };
126
+ return [{
127
+ ...step,
128
+ node_type: taggedName(step.node_type) ?? "Unknown",
129
+ edge_type: taggedName(step.edge_type)
130
+ }];
131
+ });
132
+ }
32
133
  var MindGraphError = class extends Error {
33
134
  constructor(message, status, body) {
34
135
  super(message);
35
136
  this.status = status;
36
137
  this.body = body;
37
138
  this.name = "MindGraphError";
139
+ const fields = errorFields(body);
140
+ this.code = fields.code;
141
+ this.retriable = fields.retriable;
38
142
  }
39
143
  };
40
144
  var MindGraph = class {
@@ -52,6 +156,18 @@ var MindGraph = class {
52
156
  this.telemetrySurface = config.telemetrySurface;
53
157
  this.maxRetries = config.maxRetries ?? 3;
54
158
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
159
+ if (!Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0) {
160
+ throw new RangeError("maxRetries must be a non-negative safe integer");
161
+ }
162
+ if (!Number.isFinite(this.retryBackoffMs) || this.retryBackoffMs < 0) {
163
+ throw new RangeError("retryBackoffMs must be finite and non-negative");
164
+ }
165
+ if (config.timeoutMs !== void 0) {
166
+ if (!Number.isFinite(config.timeoutMs) || config.timeoutMs <= 0) {
167
+ throw new RangeError("timeoutMs must be a positive finite number of milliseconds");
168
+ }
169
+ this.timeoutMs = config.timeoutMs;
170
+ }
55
171
  }
56
172
  // ---- HTTP helpers ----
57
173
  async request(method, path, body) {
@@ -68,9 +184,14 @@ var MindGraph = class {
68
184
  if (body !== void 0) {
69
185
  init.body = JSON.stringify(body);
70
186
  }
187
+ const retrySafe = canRetryRequest(
188
+ method,
189
+ path,
190
+ typeof init.body === "string" ? JSON.parse(init.body) : void 0
191
+ );
71
192
  let lastError;
72
193
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
73
- const res = await fetch(url, init);
194
+ const res = await this.fetchWithDeadline(url, init, method, path);
74
195
  if (!res.ok) {
75
196
  const text2 = await res.text().catch(() => "");
76
197
  let parsed;
@@ -84,10 +205,9 @@ var MindGraph = class {
84
205
  res.status,
85
206
  parsed
86
207
  );
87
- if (res.status === 503 && attempt < this.maxRetries) {
208
+ if (res.status === 503 && retrySafe && permitsRetry(err) && attempt < this.maxRetries) {
88
209
  lastError = err;
89
- const retryAfterSecs = Number(res.headers.get("retry-after"));
90
- const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
210
+ const delay = retryDelayMs(res.headers.get("retry-after"), this.retryBackoffMs, attempt);
91
211
  await new Promise((r) => setTimeout(r, delay));
92
212
  continue;
93
213
  }
@@ -99,6 +219,33 @@ var MindGraph = class {
99
219
  }
100
220
  throw lastError;
101
221
  }
222
+ /**
223
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
224
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
225
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
226
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
227
+ */
228
+ async fetchWithDeadline(url, init, method, path) {
229
+ if (this.timeoutMs === void 0) {
230
+ return fetch(url, init);
231
+ }
232
+ const controller = new AbortController();
233
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
234
+ try {
235
+ return await fetch(url, { ...init, signal: controller.signal });
236
+ } catch (error) {
237
+ if (controller.signal.aborted) {
238
+ throw new MindGraphError(
239
+ `${method} ${path} timed out after ${this.timeoutMs}ms`,
240
+ 0,
241
+ { error: "timeout", code: "timeout", retriable: false }
242
+ );
243
+ }
244
+ throw error;
245
+ } finally {
246
+ clearTimeout(timer);
247
+ }
248
+ }
102
249
  get(path) {
103
250
  return this.request("GET", path);
104
251
  }
@@ -348,6 +495,40 @@ var MindGraph = class {
348
495
  async memorySync(req) {
349
496
  return this.post("/memory/sync", req);
350
497
  }
498
+ // ---- Memory (fast path) ----
499
+ /**
500
+ * Store a small piece of text as a memory, synchronously. The node is
501
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
502
+ * Pass `custom_id` to make re-sends an upsert of the same node.
503
+ */
504
+ async remember(text, options = {}) {
505
+ return this.post("/memory/remember", { text, ...options });
506
+ }
507
+ /**
508
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
509
+ * connected edges). `dry_run: true` previews the affected edge uids first.
510
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
511
+ */
512
+ async forget(target, options = {}) {
513
+ return this.post("/memory/forget", { ...target, ...options });
514
+ }
515
+ /**
516
+ * Operator guidance for what agents should remember into a Space; surfaced
517
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
518
+ */
519
+ async setRememberInstructions(text, options = {}) {
520
+ return this.post("/memory/config", {
521
+ action: "set_remember_instructions",
522
+ text,
523
+ ...options
524
+ });
525
+ }
526
+ async getRememberInstructions(options = {}) {
527
+ return this.post("/memory/config", {
528
+ action: "get_remember_instructions",
529
+ ...options
530
+ });
531
+ }
351
532
  // ---- Agent Layer ----
352
533
  async plan(req) {
353
534
  return this.post("/agent/plan", req);
@@ -570,7 +751,7 @@ var MindGraph = class {
570
751
  start_uid: uid,
571
752
  max_depth: maxDepth
572
753
  });
573
- return r?.steps ?? r;
754
+ return pathSteps(r);
574
755
  }
575
756
  async neighborhood(uid, maxDepth = 1) {
576
757
  const r = await this.post("/traverse", {
@@ -578,7 +759,7 @@ var MindGraph = class {
578
759
  start_uid: uid,
579
760
  max_depth: maxDepth
580
761
  });
581
- return r?.steps ?? r;
762
+ return pathSteps(r);
582
763
  }
583
764
  async subgraph(uid, opts) {
584
765
  return this.post("/subgraph", { start_uids: [uid], ...opts });
package/dist/index.mjs CHANGED
@@ -1,13 +1,117 @@
1
+ // src/retry.ts
2
+ var READ_POSTS = /* @__PURE__ */ new Set([
3
+ "/search",
4
+ "/retrieve/context",
5
+ "/nodes/batch",
6
+ "/edges/batch",
7
+ "/subgraph",
8
+ "/embeddings/search",
9
+ "/embeddings/search-text",
10
+ "/ontology/query",
11
+ "/ontology/query/structured",
12
+ "/ontology/objects/search"
13
+ ]);
14
+ var READ_ACTIONS = {
15
+ "/retrieve": [
16
+ "text",
17
+ "semantic",
18
+ "hybrid",
19
+ "active_goals",
20
+ "open_questions",
21
+ "preferences",
22
+ "weak_claims",
23
+ "pending_approvals",
24
+ "unresolved_contradictions",
25
+ "merge_candidates",
26
+ "stale_derivations",
27
+ "curation_counts",
28
+ "layer",
29
+ "recent"
30
+ ],
31
+ "/traverse": ["chain", "top_k_paths", "neighborhood", "path", "subgraph"],
32
+ "/reality/series": ["window", "aggregate", "latest", "list_for_entity", "batch_latest", "batch_aggregate"],
33
+ "/intent/deliberation": ["get_open"],
34
+ "/action/risk": ["get_assessments"],
35
+ "/action/skill": ["get", "list"],
36
+ "/memory/config": ["get_preferences", "get_policies"],
37
+ "/memory/sync": ["status"],
38
+ "/agent/plan": ["get_plan", "resume_work"],
39
+ "/agent/governance": ["get_pending", "check"],
40
+ "/agent/execution": ["get_executions"],
41
+ "/evolve": ["history", "snapshot"]
42
+ };
43
+ var IDEMPOTENT_WORK = /* @__PURE__ */ new Set([
44
+ "claim_task",
45
+ "heartbeat",
46
+ "start_iteration",
47
+ "checkpoint_iteration",
48
+ "block_task",
49
+ "complete_task",
50
+ "abandon_iteration"
51
+ ]);
52
+ function errorFields(body) {
53
+ if (!body || typeof body !== "object" || Array.isArray(body)) return {};
54
+ const fields = body;
55
+ return {
56
+ ...typeof fields.code === "string" ? { code: fields.code } : {},
57
+ ...typeof fields.retriable === "boolean" ? { retriable: fields.retriable } : {}
58
+ };
59
+ }
60
+ function canRetryRequest(method, path, body) {
61
+ if (method === "GET" || method === "HEAD") return true;
62
+ if (method !== "POST") return false;
63
+ if (READ_POSTS.has(path)) return true;
64
+ const fields = body && typeof body === "object" && !Array.isArray(body) ? body : {};
65
+ if (typeof fields.action !== "string") return false;
66
+ if (Object.prototype.hasOwnProperty.call(READ_ACTIONS, path) && READ_ACTIONS[path].includes(fields.action)) return true;
67
+ return path === "/agent/plan" && IDEMPOTENT_WORK.has(fields.action) && typeof fields.idempotency_key === "string" && fields.idempotency_key.trim().length > 0;
68
+ }
69
+ function permitsRetry(fields) {
70
+ return fields.retriable !== false && ![
71
+ "query_memory_budget_exceeded",
72
+ "query_timeout",
73
+ "query_cancelled",
74
+ "vector_index_rebuilding"
75
+ ].includes(fields.code ?? "");
76
+ }
77
+ function retryDelayMs(header, backoff, attempt) {
78
+ const hint = Number(header);
79
+ return Number.isFinite(hint) && hint > 0 ? Math.min(hint * 1e3, 1e4) : Math.min(backoff * 2 ** Math.min(attempt, 32), 1e4);
80
+ }
81
+
1
82
  // src/client.ts
2
83
  function seg(value) {
3
84
  return encodeURIComponent(String(value));
4
85
  }
86
+ function pathSteps(response) {
87
+ const wrapped = response && typeof response === "object" && !Array.isArray(response) ? response : void 0;
88
+ const raw = wrapped?.steps ?? response;
89
+ if (!Array.isArray(raw)) return [];
90
+ return raw.flatMap((value) => {
91
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
92
+ const step = value;
93
+ const taggedName = (tagged) => {
94
+ if (typeof tagged === "string") return tagged;
95
+ if (!tagged || typeof tagged !== "object" || Array.isArray(tagged)) return null;
96
+ const custom = tagged.Custom;
97
+ return typeof custom === "string" ? custom : null;
98
+ };
99
+ return [{
100
+ ...step,
101
+ node_type: taggedName(step.node_type) ?? "Unknown",
102
+ edge_type: taggedName(step.edge_type)
103
+ }];
104
+ });
105
+ }
5
106
  var MindGraphError = class extends Error {
6
107
  constructor(message, status, body) {
7
108
  super(message);
8
109
  this.status = status;
9
110
  this.body = body;
10
111
  this.name = "MindGraphError";
112
+ const fields = errorFields(body);
113
+ this.code = fields.code;
114
+ this.retriable = fields.retriable;
11
115
  }
12
116
  };
13
117
  var MindGraph = class {
@@ -25,6 +129,18 @@ var MindGraph = class {
25
129
  this.telemetrySurface = config.telemetrySurface;
26
130
  this.maxRetries = config.maxRetries ?? 3;
27
131
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
132
+ if (!Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0) {
133
+ throw new RangeError("maxRetries must be a non-negative safe integer");
134
+ }
135
+ if (!Number.isFinite(this.retryBackoffMs) || this.retryBackoffMs < 0) {
136
+ throw new RangeError("retryBackoffMs must be finite and non-negative");
137
+ }
138
+ if (config.timeoutMs !== void 0) {
139
+ if (!Number.isFinite(config.timeoutMs) || config.timeoutMs <= 0) {
140
+ throw new RangeError("timeoutMs must be a positive finite number of milliseconds");
141
+ }
142
+ this.timeoutMs = config.timeoutMs;
143
+ }
28
144
  }
29
145
  // ---- HTTP helpers ----
30
146
  async request(method, path, body) {
@@ -41,9 +157,14 @@ var MindGraph = class {
41
157
  if (body !== void 0) {
42
158
  init.body = JSON.stringify(body);
43
159
  }
160
+ const retrySafe = canRetryRequest(
161
+ method,
162
+ path,
163
+ typeof init.body === "string" ? JSON.parse(init.body) : void 0
164
+ );
44
165
  let lastError;
45
166
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
46
- const res = await fetch(url, init);
167
+ const res = await this.fetchWithDeadline(url, init, method, path);
47
168
  if (!res.ok) {
48
169
  const text2 = await res.text().catch(() => "");
49
170
  let parsed;
@@ -57,10 +178,9 @@ var MindGraph = class {
57
178
  res.status,
58
179
  parsed
59
180
  );
60
- if (res.status === 503 && attempt < this.maxRetries) {
181
+ if (res.status === 503 && retrySafe && permitsRetry(err) && attempt < this.maxRetries) {
61
182
  lastError = err;
62
- const retryAfterSecs = Number(res.headers.get("retry-after"));
63
- const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
183
+ const delay = retryDelayMs(res.headers.get("retry-after"), this.retryBackoffMs, attempt);
64
184
  await new Promise((r) => setTimeout(r, delay));
65
185
  continue;
66
186
  }
@@ -72,6 +192,33 @@ var MindGraph = class {
72
192
  }
73
193
  throw lastError;
74
194
  }
195
+ /**
196
+ * One HTTP attempt. With `timeoutMs` set, the attempt is aborted at the
197
+ * deadline and surfaces as a non-retriable `MindGraphError` (`code:
198
+ * "timeout"`, `status: 0`). Without it the request is sent unchanged — no
199
+ * AbortSignal is attached, preserving the pre-0.16 behaviour.
200
+ */
201
+ async fetchWithDeadline(url, init, method, path) {
202
+ if (this.timeoutMs === void 0) {
203
+ return fetch(url, init);
204
+ }
205
+ const controller = new AbortController();
206
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
207
+ try {
208
+ return await fetch(url, { ...init, signal: controller.signal });
209
+ } catch (error) {
210
+ if (controller.signal.aborted) {
211
+ throw new MindGraphError(
212
+ `${method} ${path} timed out after ${this.timeoutMs}ms`,
213
+ 0,
214
+ { error: "timeout", code: "timeout", retriable: false }
215
+ );
216
+ }
217
+ throw error;
218
+ } finally {
219
+ clearTimeout(timer);
220
+ }
221
+ }
75
222
  get(path) {
76
223
  return this.request("GET", path);
77
224
  }
@@ -321,6 +468,40 @@ var MindGraph = class {
321
468
  async memorySync(req) {
322
469
  return this.post("/memory/sync", req);
323
470
  }
471
+ // ---- Memory (fast path) ----
472
+ /**
473
+ * Store a small piece of text as a memory, synchronously. The node is
474
+ * BM25- and vector-searchable when this resolves (`searchable` says which).
475
+ * Pass `custom_id` to make re-sends an upsert of the same node.
476
+ */
477
+ async remember(text, options = {}) {
478
+ return this.post("/memory/remember", { text, ...options });
479
+ }
480
+ /**
481
+ * Reversibly remove a memory by `uid` or `custom_id` (tombstone plus its
482
+ * connected edges). `dry_run: true` previews the affected edge uids first.
483
+ * Undo with `restore(uid)` and `evolve({ action: "restore_edge", uid })`.
484
+ */
485
+ async forget(target, options = {}) {
486
+ return this.post("/memory/forget", { ...target, ...options });
487
+ }
488
+ /**
489
+ * Operator guidance for what agents should remember into a Space; surfaced
490
+ * on every `remember()` response and on `retrieveContext()`. Empty clears it.
491
+ */
492
+ async setRememberInstructions(text, options = {}) {
493
+ return this.post("/memory/config", {
494
+ action: "set_remember_instructions",
495
+ text,
496
+ ...options
497
+ });
498
+ }
499
+ async getRememberInstructions(options = {}) {
500
+ return this.post("/memory/config", {
501
+ action: "get_remember_instructions",
502
+ ...options
503
+ });
504
+ }
324
505
  // ---- Agent Layer ----
325
506
  async plan(req) {
326
507
  return this.post("/agent/plan", req);
@@ -543,7 +724,7 @@ var MindGraph = class {
543
724
  start_uid: uid,
544
725
  max_depth: maxDepth
545
726
  });
546
- return r?.steps ?? r;
727
+ return pathSteps(r);
547
728
  }
548
729
  async neighborhood(uid, maxDepth = 1) {
549
730
  const r = await this.post("/traverse", {
@@ -551,7 +732,7 @@ var MindGraph = class {
551
732
  start_uid: uid,
552
733
  max_depth: maxDepth
553
734
  });
554
- return r?.steps ?? r;
735
+ return pathSteps(r);
555
736
  }
556
737
  async subgraph(uid, opts) {
557
738
  return this.post("/subgraph", { start_uids: [uid], ...opts });
package/package.json CHANGED
@@ -1,20 +1,33 @@
1
1
  {
2
2
  "name": "mindgraph",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "TypeScript client for the MindGraph Cloud API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
- "files": ["dist"],
8
+ "files": [
9
+ "dist"
10
+ ],
9
11
  "scripts": {
12
+ "prepare": "npm run build",
10
13
  "build": "tsup src/index.ts --format cjs,esm --dts",
11
14
  "lint": "tsc --noEmit",
12
15
  "test": "vitest run",
13
16
  "test:offline": "vitest run src/offline.test.ts",
14
17
  "test:e2e": "vitest run src/integration.test.ts"
15
18
  },
16
- "keywords": ["mindgraph", "knowledge-graph", "cognitive", "ai", "agent"],
19
+ "keywords": [
20
+ "mindgraph",
21
+ "knowledge-graph",
22
+ "cognitive",
23
+ "ai",
24
+ "agent"
25
+ ],
17
26
  "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/shuruheel/mindgraph-ts.git"
30
+ },
18
31
  "devDependencies": {
19
32
  "@types/node": "^25.4.0",
20
33
  "tsup": "^8.0.0",