Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migrating from 0.6.x to 0.7.0

Harn 0.7.0 replaces the implicit transcript_policy dict with first-class sessions. Session lifecycle is driven by imperative builtins, and unknown inputs hard-error instead of silently no-op’ing.

This guide lists every removed surface with a side-by-side rewrite.

transcript_policy on workflow nodes

The per-node policy dict is gone. Its fields moved to two dedicated setters plus lifecycle verbs.

Before (0.6)

workflow_set_transcript_policy(graph, "summarize", {
  mode: "reset",
  visibility: "public",
  auto_compact: true,
  compact_threshold: 8000,
  compact_strategy: "truncate",
  keep_last: 6,
})

After (0.7)

// Shape the node's compaction behavior:
workflow_set_auto_compact(graph, "summarize", {
  auto_compact: true,
  compact_threshold: 8000,
  compact_strategy: "truncate",
  keep_last: 6,
})
workflow_set_output_visibility(graph, "summarize", "public")

// To reset the stage's conversation explicitly before execution,
// open a caller-controlled session and wire it into the node's
// model_policy:
let sid = agent_session_open("summarize-v2")
workflow_set_model_policy(graph, "summarize", {session_id: sid})
agent_session_reset(sid)

mode: "fork" maps to agent_session_fork(src, dst?) called before workflow_execute, wiring the fork id into the node’s model_policy.session_id. mode: "continue" is the new default — two stages sharing a session_id share a conversation automatically.

transcript_id / transcript_metadata on llm_call

Both keys were removed. Session id subsumes them.

Before

let result = llm_call("hi", {
  transcript_id: "chat-42",
  transcript_metadata: {user: "ada"},
})

After

// `session_id` is honored by `agent_loop`; `llm_call` is single-shot.
// For conversational continuity, move to agent_loop:
let sid = agent_session_open("chat-42")
let result = agent_loop("hi", nil, {session_id: sid})

If you relied on the transcript_metadata bag, attach it to the session via your own store or pass per-call context through the metadata field of injected messages. transcript_summary (per-call summary injection for mid-loop compaction output) is unchanged.

transcript option on llm_call / agent_loop

Passing a raw transcript dict through the transcript option is now a hard error.

Before

let t = transcript()
let result = agent_loop("task", nil, {transcript: t, provider: "mock"})

After

let sid = agent_session_open()
let result = agent_loop("task", nil, {session_id: sid, provider: "mock"})
// `agent_session_snapshot(sid)` if you want the transcript back as a dict.

The loop loads prior messages from the session store as a prefix before running and persists the final transcript back on exit.

Lifecycle via dict (mode: "reset" | "fork")

Previously some call sites accepted a lifecycle dict. That pattern is gone — call the verbs explicitly:

  • mode: "reset"agent_session_reset(id)
  • mode: "fork"let dst = agent_session_fork(src) (optionally with a caller-provided dst id)
  • mode: "continue" → no-op; just reuse the same session_id

Subscribers

CLOSURE_SUBSCRIBERS (thread-local in agent_events.rs) was removed. Subscribers now live on SessionState.subscribers.

  • agent_subscribe(id, cb) opens the session lazily and appends.
  • agent_session_fork does not copy subscribers — a fork is a conversation branch, not an event fanout.
  • clear_session_sinks only clears external ACP-style sinks now; it no longer evicts sessions.

Unknown-key / unknown-id behavior

A class of silent pass-throughs is now an error:

  • Unknown agent_session_compact option keys.
  • Missing role on agent_session_inject.
  • Negative keep_last.
  • reset / fork / close / trim / inject / length / compact called against an unknown session id.

exists, open, and snapshot remain tolerant of unknown ids by design.

agent_loop terminal status

max_iterations reached without a natural break now reports status = "budget_exhausted" (previously "done"). If your host keys off "done" to detect “agent is finished,” add "budget_exhausted" to the accept list — the loop ran out of rope, not out of work. Daemon loops in the same condition no longer silently relabel to "idle".

See the Sessions chapter for the full model and the 0.7.0 entry in the changelog for the complete breaking-change list.