harn-stdlib 0.10.1

Embedded Harn standard library source catalog
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
// Compaction pins as DATA: a typed pin taxonomy that generalizes Burin's
// structural working-set. A pin is a small typed value `{kind, content, ...}`
// that (a) survives compaction by construction — it lowers to a
// `preserve_on_compact` system reminder AND a compaction `preserve` directive —
// and (b) doubles as a reachability-GC ROOT so any transcript message a pin
// references is never garbage-collected. Pins are plain data the caller threads
// through the existing seams (`transcript`/`agent_session_*` reminders,
// `compaction_policy`, `transcript_project`); this module adds NO new host
// surface. Burin's legacy `[no-compact]` heading marker is supported as a
// documented ingestion format via `recognize_no_compact`.
import { compaction_policy } from "std/agent/autocompact"
import { agent_session_inject_reminder } from "std/agent/state"

// -------------------------------------------------------------------------------------------------

// Pin taxonomy (alphabetical). Generalized from Burin's structural working-set
// categories: `goal` (session objective), `constraint` (guardrails / open
// task-contract clauses), `decision` (a durable choice the agent must not
// relitigate), `artifact_ref` (a live file-view / path the work hinges on), and
// `no_compact` (a raw block the caller marked keep-verbatim — the generalization
// of the `[no-compact]` marker). `role_hint` mirrors Burin: binding context is
// "system", live evidence is "developer".

// -------------------------------------------------------------------------------------------------

const __PIN_KINDS = ["artifact_ref", "constraint", "decision", "goal", "no_compact"]

const __NO_COMPACT_MARKER = "[no-compact]"

/**
 * PinSpec is the normalized shape returned by `pin(...)`: a typed pin the
 * caller threads through the compaction/reachability seams. `kind` is one of
 * `pin_kinds()`; `content` is the verbatim text to preserve; `dedupe_key` makes
 * a pin self-replacing across re-injection; `role_hint` selects the reminder
 * lane ("system" | "developer").
 */
pub type PinSpec = {
  id?: string,
  kind: string,
  content: string,
  dedupe_key?: string,
  role_hint?: string,
  meta?: dict,
}

/**
 * pin_kinds returns the registered pin taxonomy (sorted). Discovery surface for
 * tooling and validation.
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 */
pub fn pin_kinds() {
  return __PIN_KINDS
}

// kind -> reminder lane, as data. Binding context (goal/constraint/no_compact)
// rides the "system" lane so the model treats it as governing; live evidence
// (artifact_ref/decision) rides the "developer" lane. Mirrors Burin's
// structural working-set role_hints.
const __PIN_ROLE_HINTS = {
  artifact_ref: "developer",
  constraint: "system",
  decision: "developer",
  goal: "system",
  no_compact: "system",
}

fn __pin_role_hint(kind) {
  return __PIN_ROLE_HINTS[kind] ?? "system"
}

// Taxonomy-registration seam (ADDITIVE, default-off). The fixed 5 kinds are the
// generic taxonomy; a caller (e.g. burin's structural working-set with
// guardrails/task_contract/file_view/remaining_failures/verification_hud) may
// widen the accepted set for a single `pin(...)` call via `opts.register_kinds`
// instead of `pin()` throwing on its domain kinds. When `register_kinds` is
// unset the accepted set is EXACTLY `__PIN_KINDS`, so validation — and its
// throw message — is byte-identical to the pre-seam behavior.
fn __pin_allowed_kinds(options) {
  const extra = options?.register_kinds ?? []
  if type_of(extra) != "list" {
    throw "pin: `register_kinds` must be a list of kind strings"
  }
  if len(extra) == 0 {
    return __PIN_KINDS
  }
  let out = __PIN_KINDS
  for raw in extra {
    const k = to_string(raw ?? "")
    if k != "" && !contains(out, k) {
      out = out.push(k)
    }
  }
  return out
}

/**
 * pin(kind, content, opts?) builds a normalized PinSpec. `kind` must be one of
 * `pin_kinds()` (or, additively, of the caller taxonomy named in
 * `opts.register_kinds`); `content` must be a non-empty string. `opts` may
 * carry `id`, `dedupe_key`, `role_hint`, `meta`, and `register_kinds`.
 * `dedupe_key` defaults to a per-pin key; pass a stable value (e.g.
 * `"pin/goal"`) for a self-replacing singleton pin. A caller-registered kind
 * with no entry in the role map defaults to the "system" role unless
 * `role_hint` is supplied.
 *
 * @effects: []
 * @errors: [runtime]
 * @api_stability: stable
 * @example: pin("goal", "Ship the migration", {dedupe_key: "pin/goal"})
 */
pub fn pin(kind, content, opts = nil) {
  const options = if type_of(opts) == "dict" {
    opts
  } else {
    {}
  }
  const allowed = __pin_allowed_kinds(options)
  if type_of(kind) != "string" || !contains(allowed, kind) {
    throw "pin(kind, content, opts?): kind must be one of " + join(allowed, ", ") + "; got "
      + to_string(kind)
  }
  if type_of(content) != "string" || content == "" {
    throw "pin(\"" + kind + "\", content): content must be a non-empty string"
  }
  const id = options?.id ?? ("pin_" + uuid_v7())
  return {
    content: content,
    dedupe_key: options?.dedupe_key ?? ("pin/" + kind + "/" + id),
    id: id,
    kind: kind,
    meta: options?.meta ?? {},
    role_hint: options?.role_hint ?? __pin_role_hint(kind),
  }
}

/**
 * unpin(pins, pin_id) returns the pin collection with the pin whose `id`
 * matches removed. Pure list operation over a caller-held collection.
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 */
pub fn unpin(pins, pin_id) {
  let out = []
  for p in pins ?? [] {
    if p?.id != pin_id {
      out = out.push(p)
    }
  }
  return out
}

/**
 * pin_reachability_roots(pins) returns the list of pin contents to feed into a
 * `reachability_gc` projection as caller-supplied roots. Any stale tool result
 * whose identifiers appear in a pinned content string is then kept, not
 * garbage-collected. Extends the existing `roots` config of
 * `transcript_project`/`agent_session_project_turn` — not a parallel mechanism.
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 */
pub fn pin_reachability_roots(pins) {
  let roots = []
  for p in pins ?? [] {
    if type_of(p?.content) == "string" && p.content != "" {
      roots = roots.push(p.content)
    }
  }
  return roots
}

/**
 * with_pin_roots(project_options, pins) merges the pins' reachability roots into
 * a `transcript_project`/`agent_session_project_turn` options dict (policy
 * `reachability_gc`), preserving any roots the caller already supplied.
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 * @example: transcript_project(t, with_pin_roots({policy: "reachability_gc"}, pins))
 */
pub fn with_pin_roots(project_options, pins) {
  const opts = if type_of(project_options) == "dict" {
    project_options
  } else {
    {}
  }
  let existing = []
  if type_of(opts?.roots) == "list" {
    existing = opts.roots
  } else if type_of(opts?.roots) == "string" {
    existing = [opts.roots]
  }
  return opts + {roots: existing + pin_reachability_roots(pins)}
}

fn __pin_policy_merge_lists(existing, additions) {
  let out = []
  for v in existing ?? [] {
    const s = to_string(v ?? "")
    if s != "" && !contains(out, s) {
      out = out.push(s)
    }
  }
  for v in additions ?? [] {
    const s = to_string(v ?? "")
    if s != "" && !contains(out, s) {
      out = out.push(s)
    }
  }
  return out
}

fn __pin_policy_join_instructions(existing, own) {
  const a = to_string(existing ?? "").trim()
  const b = to_string(own ?? "").trim()
  if a == "" {
    return if b == "" {
      nil
    } else {
      own
    }
  }
  if b == "" {
    return existing
  }
  return a + "\n" + b
}

/**
 * pin_compaction_policy(pins, opts?) builds a `compaction_policy` whose
 * `preserve` directives instruct the summarizer to keep every pinned block
 * verbatim — so pins survive LLM-summarizing compaction by construction. Wires
 * the pins into the existing `CompactionPolicy.preserve` seam.
 *
 * ADDITIVE opts (default-off — with neither key set the result is
 * byte-identical to the pre-seam behavior): `merge` (an existing policy dict)
 * UNIONS the pins' `preserve` with that policy's `preserve`/`drop` and
 * concatenates its `instructions`, so a caller can fold pins into a policy it
 * already assembled instead of replacing it; `preserve_labels: true` (only
 * meaningful with `merge`) keeps the existing policy's `mode`/`author` labels
 * rather than stamping the pins defaults. The `merge`/`preserve_labels` control
 * keys are stripped and never leak into the emitted policy dict.
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 */
pub fn pin_compaction_policy(pins, opts = nil) {
  const raw = if type_of(opts) == "dict" {
    opts
  } else {
    {}
  }
  const base = __dict_omit(raw, ["merge", "preserve_labels"])
  let pin_preserve = []
  for p in pins ?? [] {
    if type_of(p?.content) == "string" && p.content != "" {
      pin_preserve = pin_preserve
        .push(
        "Preserve the pinned " + to_string(p?.kind ?? "note") + ": "
          + p.content,
      )
    }
  }
  const merge_with = if type_of(raw?.merge) == "dict" {
    raw.merge
  } else {
    nil
  }
  if merge_with == nil {
    // Default path — byte-identical to the pre-seam behavior.
    return compaction_policy(
      base?.instructions,
      base
        + {
        author: base?.author ?? "pins",
        mode: base?.mode ?? "pins",
        preserve: base?.preserve ?? pin_preserve,
      },
    )
  }
  // Merge-with-existing (+ optional label-preserve).
  const preserve_labels = raw?.preserve_labels ?? false
  const preserve = __pin_policy_merge_lists(merge_with?.preserve, base?.preserve ?? pin_preserve)
  const drop = __pin_policy_merge_lists(merge_with?.drop, base?.drop)
  const author = if preserve_labels && to_string(merge_with?.author ?? "") != "" {
    merge_with.author
  } else {
    base?.author ?? "pins"
  }
  const mode = if preserve_labels && to_string(merge_with?.mode ?? "") != "" {
    merge_with.mode
  } else {
    base?.mode ?? "pins"
  }
  const instructions = __pin_policy_join_instructions(merge_with?.instructions, base?.instructions)
  let merged = base + {author: author, mode: mode, preserve: preserve}
  if len(drop) > 0 {
    merged = merged + {drop: drop}
  }
  return compaction_policy(instructions, merged)
}

fn __pin_reminder_validate(options) {
  const propagate = options?.propagate
  if propagate != nil && type_of(propagate) != "string" {
    throw "pin_reminder: `propagate` must be a string (e.g. \"session\")"
  }
  const ns = options?.tag_namespace
  if ns != nil && type_of(ns) != "string" {
    throw "pin_reminder: `tag_namespace` must be a string"
  }
  const limit = options?.max_body
  if limit != nil && type_of(limit) != "int" {
    throw "pin_reminder: `max_body` must be an int char budget"
  }
  return options
}

fn __pin_reminder_body(content, options) {
  const limit = options?.max_body
  const text = to_string(content ?? "")
  if type_of(limit) != "int" || limit <= 0 || len(text) <= limit {
    return content
  }
  return text.substring(0, limit) + "\n[pin reminder truncated]"
}

fn __pin_reminder_tags(base_tags, options) {
  const ns = options?.tag_namespace
  if type_of(ns) != "string" || ns == "" {
    return base_tags
  }
  let out = [ns]
  for t in base_tags {
    if !contains(out, t) {
      out = out.push(t)
    }
  }
  return out
}

/**
 * pin_reminder(pin, opts?) lowers a PinSpec to a system-reminder options dict
 * with `preserve_on_compact: true`, `tags: ["pin", <kind>]`, the pin's
 * `dedupe_key`, and its `role_hint`. Spread it into
 * `transcript.inject_reminder(...)` or `agent_session_inject_reminder(...)` —
 * this is the "survives compaction by construction" mechanism (the
 * structured-metadata twin of `[no-compact]`).
 *
 * ADDITIVE opts (all default-off — with `opts` unset the returned dict is
 * byte-identical to the pre-seam shape): `propagate` (e.g. `"session"`) adds a
 * `propagate` key so the reminder re-injects across a whole session;
 * `max_body` clamps an over-long body to a char budget with a truncation
 * marker; `tag_namespace` leads the `tags` list with a caller namespace tag
 * (e.g. `"burin_structural_working_set"`) ahead of `["pin", <kind>]`.
 *
 * @effects: []
 * @errors: [runtime]
 * @api_stability: stable
 */
pub fn pin_reminder(pin, opts = nil) {
  const raw = if type_of(opts) == "dict" {
    opts
  } else {
    {}
  }
  const options = __pin_reminder_validate(raw)
  const kind = to_string(pin?.kind ?? "note")
  let reminder = {
    body: __pin_reminder_body(pin.content, options),
    dedupe_key: pin?.dedupe_key ?? ("pin/" + kind),
    preserve_on_compact: true,
    role_hint: pin?.role_hint ?? __pin_role_hint(kind),
    tags: __pin_reminder_tags(["pin", kind], options),
  }
  const propagate = options?.propagate
  if type_of(propagate) == "string" && propagate != "" {
    reminder = reminder + {propagate: propagate}
  }
  return reminder
}

/**
 * agent_pin(session_id, pin) persists a pin into a live session by injecting its
 * `preserve_on_compact` reminder. Returns the reminder id. The pin re-renders
 * each turn and is carried across compaction by the reminder lifecycle.
 *
 * @effects: [host]
 * @errors: [runtime]
 * @api_stability: stable
 */
pub fn agent_pin(session_id, pin) {
  return agent_session_inject_reminder(session_id, pin_reminder(pin))
}

/**
 * strip_no_compact(text) removes Burin's `[no-compact]` heading marker and the
 * double-space it leaves at end-of-line. Documented ingestion adapter, mirrors
 * Burin's `strip_no_compact_marker`.
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 */
pub fn strip_no_compact(text) {
  return to_string(text).replace(__NO_COMPACT_MARKER, "").replace("  \n", "\n")
}

/**
 * recognize_no_compact(text, opts?) is an input adapter for Burin's `[no-compact]`
 * message-text marker: if `text` carries the marker it returns a `no_compact`
 * pin whose content is the marker-stripped text, else nil. This is an ingestion
 * FORMAT recognizer (not a back-compat shim for a removed API).
 *
 * @effects: []
 * @errors: []
 * @api_stability: stable
 * @example: recognize_no_compact("## Session goal [no-compact]\nObjective: X")
 */
pub fn recognize_no_compact(text, opts = nil) {
  if type_of(text) != "string" || !text.contains(__NO_COMPACT_MARKER) {
    return nil
  }
  return pin("no_compact", strip_no_compact(text), opts)
}