# Migrating to FlowCraft `sdk/v0.3.0`

> Status: `sdk/v0.3.0` shipped; `sdkx/v0.3.0` follows once this PR
> bumps the `sdkx → sdk` dep pin and the tag is pushed. The
> per-package `deprecated.go` index files that tracked these symbols
> during the v0.2.x deprecation window are gone; this document is
> the single source of truth for the migration.

## Summary

`sdk/v0.3.0` is the cleanup release that finishes the multi-layered
deprecation work done across `v0.1.x` and `v0.2.x`. Three themes drive
it:

1. **Layering** — `sdk` defines interfaces and primitives, `sdkx` ships
   concrete adapters. LLM tool implementations move to `sdkx/tool/...`.
2. **Runtime convergence** — the `agent + engine + graph` runtime fully
   replaces the legacy `workflow` package and the `llm.RunRound` round
   helper family. The unexported
   `sdk/graph/runner/internal/executor.Executor` interface retires in
   favour of `Runner` directly implementing `engine.Engine` over a
   single `LocalExecutor`.
3. **API tidy-ups** — many service packages (`sdk/knowledge`,
   `sdk/history`, `sdk/retrieval`) carry compatibility shims from their
   v0.1 → v0.2 redesigns. v0.3.0 deletes them.

> **`sdk` defines interfaces and primitives. `sdkx` ships concrete
> adapters that integrate with external systems or external protocol
> specs.**

---

## Breaking changes by package

### `sdk/workflow` — full package deletion

Deprecated since `v0.1.x`. Every workflow concept maps to an
`agent + engine + graph` equivalent: `workflow.Agent` →
`agent.Agent` driven by `runner.Runner` (which implements
`engine.Engine`); `workflow.Board` → `engine.Board`; `workflow`
strategy / runnable → `agent.BoardSeeder` + `agent.Decider`;
`workflow.RunOption` → `engine.Run` parameters.

### `sdk/graph/adapter` — workflow ↔ graph bridge package deleted

The whole package goes away with `sdk/workflow`. Removed symbols:

- `adapter.FromDefinition(*graph.GraphDefinition) workflow.Strategy`
- `adapter.FromCompiled(*graph.CompiledGraph) workflow.Strategy`

These existed only to plug `graph.GraphDefinition` /
`CompiledGraph` into `workflow.Strategy` so workflow runs could
delegate execution to graph. The replacement is to drive the
graph directly: build a `runner.Runner` (which implements
`engine.Engine`) and either call it through `agent.Run` or let
`engine.LoadAndResume` resume against it. Boards no longer need
the `graphBoardFromWorkflow` / `workflowBoardFromGraph` round-trip
because `engine.Board` is the single board type.

### `sdk/agent` — Handoff stays put

`sdk/agent.Handoff` and friends remain in `sdk` — they are an internal
agent-orchestration primitive, not an external service adapter.

### `sdk/{knowledge,kanban,history}` — LLM tool implementations move to `sdkx/tool/<service>`

The function/struct shapes are preserved; only the import path changes.

| Today (`sdk` v0.2.x)                              | After (`sdk/v0.3.0` + `sdkx/v0.3.0`)         |
| ------------------------------------------------- | -------------------------------------------- |
| `sdk/knowledge.NewSearchServiceTool`              | `sdkx/tool/knowledge.NewSearchServiceTool`   |
| `sdk/knowledge.NewPutServiceTool`                 | `sdkx/tool/knowledge.NewPutServiceTool`      |
| `sdk/kanban.SubmitTool`                           | `sdkx/tool/kanban.SubmitTool`                |
| `sdk/kanban.TaskContextTool`                      | `sdkx/tool/kanban.TaskContextTool`           |
| `sdk/kanban.WithKanban` / `sdk/kanban.KanbanFrom` | `sdkx/tool/kanban.WithKanban` / `KanbanFrom` |
| `sdk/history.ToolDeps`                            | `sdkx/tool/history.ToolDeps`                 |
| `sdk/history.RegisterTools`                       | `sdkx/tool/history.RegisterTools`            |

**New `sdkx/tool/...` packages landing alongside (no removal pressure):**

- `sdkx/tool/memory` — Anthropic Memory Tool spec-compatible wrapper
  over `sdk/workspace.Workspace`.

#### Migration

Mechanical import-path swap:

```diff
-import "github.com/GizClaw/flowcraft/sdk/knowledge"
+import knowledgetool "github.com/GizClaw/flowcraft/sdkx/tool/knowledge"

- search := knowledge.NewSearchServiceTool(svc)
+ search := knowledgetool.NewSearchServiceTool(svc)
```

#### Release coordination with `sdkx`

`sdk` and `sdkx` are independent Go modules with independent version
streams, but the tool migration straddles both. The schedule is:

| Phase      | sdk                                                | sdkx                                                                                                                                                                                                                                                                                                       |
| ---------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| v0.2.x     | tools live in `sdk` (deprecated), still functional | `sdkx/tool/{knowledge,kanban,history,memory}` published as forward-compatible wrappers. `history` is a Go type alias + delegation; `kanban` reuses the sdk-side context key                                                                                                                                |
| v0.3.0 cut | tools deleted from `sdk`                           | `sdkx` ships a coordinated release that takes ownership: `sdkx/tool/history` inlines the relocated archive helpers; `sdkx/tool/kanban` hosts its own context key. Public **signatures stay identical** — for downstream code the cut is a `go get -u` away, no source changes after the v0.2.x import swap |

Recommended user upgrade path:

1. **During v0.2.x**: change imports from `sdk/{knowledge,kanban,history}`
   to `sdkx/tool/{knowledge,kanban,history}` (mechanical text swap, see
   the diff above). Tag boundaries are not crossed.
2. **At v0.3.0**: `go get -u github.com/GizClaw/flowcraft/sdk@v0.3.0
github.com/GizClaw/flowcraft/sdkx@v0.3.0` and rebuild. No source
   edits needed.

This is exactly why the v0.2.x sdkx wrappers are deliberately a thin
forwarding layer rather than a re-implementation: it lets the
"change imports" event and the "change underlying implementation"
event happen at separate, low-risk moments for users.

### `sdk/llm` — round helpers and capability shims removed

Removed in v0.3.0:

- **Round helper family** (workflow-era streaming + tool-call orchestrator):
  `RunRound`, `RoundStream`, `StreamRound`, `RoundResult`, `RoundConfig`,
  `RoundConfigFromMap`, `CoerceMapForStruct`. The `agent + engine` runtime
  owns per-call streaming and tool execution natively — call
  `LLM.GenerateStream` directly or run the LLM through an `agent.Agent`.
- **Capability middleware shims**: `CapsMiddleware` (replaced by
  `WithCaps`), `WithExtraCaps` (renamed `WithPolicyCaps` to make the
  resolver-wide intent explicit),
  `(*ProviderRegistry).LookupModelCaps` (replaced by
  `LookupModelSpec(...).Caps`).
- **`ModelInfo.Caps` field** — set `ModelInfo.Spec.Caps` directly.

Additions (v0.2.x → v0.3.0, no removals required):

- **Image / audio output capabilities**: `CapImageOutput`,
  `CapAudioOutput`; `ImageGenOptions` request shape; the
  `ImageGenerator` capability surface.
- **`OneChunkStream`** is now a public helper for adapters that want
  to expose a non-streaming response through the streaming interface.

### `sdk/graph` — runner converges on `engine.Engine`

Removed in v0.3.0:

- **`sdk/graph/runner/internal/executor.Executor` interface** — the
  legacy execution-engine abstraction. `runner.Runner` itself
  implements `engine.Engine` starting in v0.2; the internal
  `LocalExecutor` is now the only concrete graph executor and is
  used by `Runner` directly without going through an interface.
- **`graph.ErrInterrupt`** sentinel — return
  `engine.Interrupted(engine.Interrupt{Cause: …})` instead, which
  carries a typed Cause and Detail. Both used to satisfy
  `errdefs.IsInterrupted`; only the latter remains.
- **Runner option shims** that bypass the host abstraction:
  `WithEventBus`, `WithStreamCallback`, `WithCheckpointStore`,
  `Runner.Bus()`, plus the matching internal fields on `Executor`.
  Pass an `engine.Host` via `WithHost` instead.
- **Run-ID injection helpers** that pre-date `engine.Run.ID` — the run
  identity now flows through `engine.Engine.Execute`. (`runner.WithRunID`
  itself stays exported as a non-deprecated convenience for callers
  that drive `Runner.Run` directly outside an engine handler.)
- **`busOnlyHost`** transitional shim.
- **`graph.VarMessages` / `graph.VarQuery` / `graph.VarAnswer`** removed.
  An earlier plan moved them to `sdk/agent`; instead the underlying
  abstraction was simplified: `llmnode.Config.MessagesKey` (string,
  pulling double-duty as both var name and channel name with a
  hard-coded `"messages" → "main"` rewrite) is replaced by
  `llmnode.Config.MessagesChannel` (json `messages_channel`), which
  is a pure typed-channel name. Empty value means [graph.MainChannel];
  any other value names an isolated channel. `llmnode` no longer
  mirrors the transcript onto a board var — readers must use
  `board.Channel(...)`.
- **`llmnode.Config.QueryFallback`** (json `query_fallback`) removed,
  along with the implicit "read board var `query`, append as final
  user message when the messages channel is non-Main" branch. The
  feature coupled "isolated channel" with "single-turn fallback"
  through a magic var key and had fragile multi-turn semantics.
  Migration: write the user turn directly to the channel from an
  upstream node, e.g.

  ```go
  board.SetChannel("my_chan", []llm.Message{
      llm.NewTextMessage(llm.RoleUser, query),
  })
  ```

  The compile-time warning `llm_isolated_messages_no_fallback` is also
  removed because its only resolution suggestion (set `query_fallback`)
  no longer exists; an empty isolated channel will surface as a
  runtime error from the LLM call instead.

- **`graph.ValidateInputsWithConfig`** behaviour change: the legacy
  branch `p.Name == VarMessages || p.Type == PortTypeMessages` (always
  validating against `MainChannel`) is now `p.Type == PortTypeMessages`
  validating against `Channel(p.Name)`. Custom nodes that declared a
  message-typed input port with a non-Main name and relied on the
  validator silently skipping the check by reading `MainChannel` will
  now correctly require their own channel to be populated.
- **`engine.MainChannel`** value changed from `""` to `"__main_channel"`.
  Naming rule introduced: every engine-reserved board key (var or
  channel) uses the `__` prefix; user-domain code MUST NOT introduce
  channel or var names beginning with `__`. Existing code that
  references `engine.MainChannel` / `graph.MainChannel` via the
  constant tracks the new value automatically — only callers that
  hard-coded the empty string need to switch to the constant. Stored
  checkpoints (`BoardSnapshot.Channels` JSON) carrying the legacy
  `""` key are auto-migrated by `engine.RestoreBoard` /
  `Board.RestoreFrom`; the migration shim is scheduled for removal
  in v0.4 once all persisted checkpoints have been re-taken with a
  v0.3+ writer.
- **`llmnode` empty-request guard**: `Node.ExecuteBoard` now returns
  `errdefs.Validation` when the resolved `messages_channel` is empty
  _and_ `system_prompt` is unset. Previously such a graph would hand
  an empty request to the LLM, where behaviour depends on the
  provider (Anthropic rejects, OpenAI / Gemini accept). The
  system-prompt-only case (channel empty but `system_prompt` non-
  empty) is still allowed — useful for autonomous / scheduled agents
  whose first turn is system-driven.
- **`knowledgenode.Config.QueryKey`** added (json `query_key`). Names
  the board var the node reads as the search query; empty preserves
  the historical `"query"` default. Use it to disambiguate when
  multiple knowledge nodes consume different inputs in the same
  graph, or to align the var key with whatever the upstream board
  seeder writes.
- **`model.LastByRole(msgs, role)`** added. Returns the last message
  in `msgs` whose `Role` matches, plus an `ok` bool. Replaces the
  previous ad-hoc reverse-scan loops sprinkled around node code that
  needed "the latest user turn on `MainChannel`" (or any role-scoped
  pick) and removes the temptation to recouple board vars and channel
  contents through magic keys.

### `sdk/script/bindings` — workflow-coupled bindings removed

Removed in v0.3.0:

- `NewStreamBridge(stream workflow.StreamCallback, nodeID string)` —
  the bridge that adapted a `workflow.StreamCallback` for script
  emission. Scripts now publish through the `engine.Host` /
  `event.Bus` plane that node code already uses; no script-side
  shim is needed.
- `NewRunBridge` / `RunBridgeOptions` — hard-wired to `*workflow.Board`.
  Replaced by `NewRunInfoBridge(info agent.RunInfo)`; the agent/engine
  stack carries the same metadata as `agent.RunInfo`.
- `AgentStepBindings` / `AgentStepOptions` — `Board *workflow.Board`
  receiver retires with `workflow`.
- The `BuildEnv` preset for the workflow-era binding set — callers
  compose `BuildEnv` with the bridges they want directly.

### `sdk/knowledge` — legacy v0.1 storage and search types

Removed in v0.3.0:

- **Data-model deletions**:
  - `Document` (split into `SourceDocument` for raw content + Version,
    and `DerivedLayer` for L0 abstract / L1 overview).
  - `Chunk` (replaced by `DerivedChunk`).
  - `SearchResult` (replaced by `Hit`).
  - `SearchOptions` (replaced by `Query` with `Scope` / `Mode` /
    `Layer`).
  - `DocInput` (folded into the `*Service.Put` parameters).
  - `ModeSemantic` (`SearchMode = "semantic"`) — use `ModeVector`. The
    v0.2.x `ResolveMode` shim that mapped `"semantic" → "vector"` is
    gone too; persisted requests carrying the literal must be
    rewritten before upgrading.
- **Graph node renames** — the v0.1 node types lived as deprecated
  shims in v0.2.x and are now removed:
  - `KnowledgeConfig` → `KnowledgeNodeConfig`
  - `KnowledgeNode` → `KnowledgeServiceNode`
  - `NewKnowledgeNode` → `NewKnowledgeServiceNode`
  - `KnowledgeConfigFromMap` → `KnowledgeNodeConfigFromMap`
  - `RegisterNode` → `RegisterServiceNode`
  - `KnowledgeNodeSchema` → `KnowledgeServiceNodeSchema`

  `DatasetQuery` (the dataset descriptor reused by both old and new
  config) is no longer exported from `sdk/knowledge`; it lives on
  `knowledgenode.Config.Datasets` directly.

- **Storage layer**: `Store` interface, `FSStore` and its options
  (`WithTokenizer`, `WithChunkConfig`, `WithEmbedder`, ...),
  `RetrievalStore` and its options (`WithRetrievalEmbedder`,
  `WithRetrievalPipeline`, `WithRetrievalChunker`,
  `WithRetrievalTokenizer`), `CachedStore` and its options
  (`WithTTL`, `WithMaxItems`). All replaced by `factory.NewLocal` /
  `factory.NewRetrieval` returning `*Service`.
- **Search/ranking helpers**: `ChunkDocument` (use `ChunkText`),
  `ScoreChunk` (use `textsearch.BM25` directly), `RankResults` /
  `RRFMerge` (use `RRFRanker`).
- **Reload pipeline**: `ChangeNotifier` / `Reloader` /
  `NewReloader` (use `EventNotifier` + `EventReloader`). The
  in-tree consumer of the deprecated notifier,
  `sdkx/knowledge/watcher`, is removed alongside; durable watchers
  must produce `ChangeEvent` values for `EventReloader` directly.
- **Tool helpers carried over from the `Store` era**: `NewSearchTool`,
  `NewAddTool` (use `NewSearchServiceTool` / `NewPutServiceTool`,
  themselves moving to `sdkx/tool/knowledge` per the layering section
  above).

Type-alias changes (kept compiling, but the primary spelling flipped):

- `Layer` is now the canonical type name; the v0.2.x primary name
  `ContextLayer` survives as `type ContextLayer = Layer` so callers
  using either spelling keep building. Constants
  (`LayerAbstract` / `LayerOverview` / `LayerDetail`) declare on
  `Layer`.
- `Mode` is the canonical name for `SearchMode`; the older spelling
  remains as `type Mode = SearchMode`. The constant set
  (`ModeBM25` / `ModeVector` / `ModeHybrid`) declares on `SearchMode`.

### `sdk/history` — closers and manifest internals

Removed in v0.3.0:

- **`Closer.Close` / `compactor.Close`** — use `Coordinator.Shutdown`
  with a context for bounded waits.
- **`SummaryCacheStore`** — superseded by `SummaryStore`, which the
  summary DAG already consumes; the cache wrapper had no remaining
  readers.
- **Top-level archive helpers moved off the public surface**:
  - `RecoverArchive(ctx, ws, store, prefix, archivePrefix, convID)` —
    folded into the unexported `recoverArchiveImpl`. `Coordinator`
    auto-recovers in-flight archives at construction; callers that
    drove recovery manually should construct a `Coordinator` instead.
  - `SaveManifest(ctx, ws, prefix, archivePrefix, convID, *Manifest)` —
    folded into the unexported `saveManifestImpl`. There is no
    public replacement: archive lifecycle is now owned by
    `Coordinator` end-to-end.
- **Tool implementations migrated to `sdkx/tool/history`** — `ToolDeps`,
  `RegisterTools`, `historyExpandTool`, `historyCompactTool`. The
  archive helpers they read from disk (`Archive`, `LoadManifest`,
  `LoadArchivedMessages`) remain exported from `sdk/history` so the
  external tool wrapper can keep using them without re-implementing
  the on-disk format.

Note: `Store` (the short-term `[]model.Message` persistence interface)
is **not** removed — it is the active contract `InMemoryStore` and the
archive subsystem are built around. It is unrelated to the
`SummaryStore` summary-DAG interface.

### `sdk/retrieval` — Execution-only debug surface

Removed in v0.3.0:

- `SearchRequest.ReturnRaw` (request-side toggle) — the pipeline no
  longer branches on this flag; debug projections are produced
  unconditionally and read through `SearchResponse.Execution`.
- `SearchResponse.RawByRetriever` (response-side debug map) — read
  `SearchResponse.Execution` instead.
- `Explain.Lanes` legacy field — use `Execution.Lanes`.

---

## Additive surface picked up between v0.2.0 and v0.3.0

The following capabilities landed during the v0.2.x cycle and ship
unchanged in v0.3.0; they are not breaking, but downstream code that
skipped intermediate v0.2.x tags often discovers them at the v0.3.0
upgrade. Listed for completeness:

- **`sdk/engine`**
  - `StreamRouter` — fan-out helper for routing a single stream-delta
    source to multiple sinks (file, OTLP, websocket, ...).
  - Optional `Resumer` interface plus the `LoadAndResume` helper —
    engines that opt in get fully threaded resume from a
    `CheckpointStore` via one call.
  - Public `Subject` / `Pattern` contract for cross-engine event
    routing.
  - Engine-host extensibility hooks (event sampling, token-usage
    reporting wiring).
- **`sdk/agent`**
  - `Handoff` DSL — declarative agent-to-agent transfer primitive.
- **`sdk/workspace`**
  - `Capabilities` value + `CapabilityReporter` interface so workspace
    backends advertise the operations they actually support
    (atomic-write, advisory-lock, ...). Adapters in `sdkx/workspace`
    and `sdkx/retrieval/workspace` rely on this to gate behaviour.
- **`sdk/retrieval/scoring`**
  - Public `CosineSim`, plus `RRFFuse` / `WeightedFuse` helpers
    extracted so external rankers can call them directly.
- **`sdk/telemetry`**
  - `AttrXxx` constant set; `ConversationID` / `DatasetID` /
    `ErrorMessage` attribute keys; provider-error classification
    aligned across providers.
- **`sdk/errdefs`**
  - Pod-grade boundary error normalization (every external boundary
    classifies origin errors through `errdefs` rather than ad-hoc
    string matching).

---

## Why this release

v0.2 introduced the `agent + engine + graph` runtime, the
`SourceDocument`/`DerivedLayer` knowledge model, and the
`Coordinator`-driven history archive. Each redesign shipped with
compatibility shims (`Deprecated:`-marked symbols + per-package
`deprecated.go` indexes) so existing call sites kept compiling
through the v0.2 series.

v0.3.0 deletes those shims in one cut, collapsing the duplicate API
surface so contributors stop threading new code through the legacy
paths and `staticcheck (SA1019)` stops being noisy.

## Backwards-compatibility

There is no global shim. The runtime removals (`workflow`,
`executor.Executor`, the round helpers) genuinely delete behaviour;
the storage/format renames in `sdk/knowledge` and `sdk/history`
preserve on-disk artefacts (the `factory.NewLocal` service reads the
same workspace layout the legacy `FSStore` produced). Stored
checkpoints carrying the legacy empty-string `MainChannel` key are
auto-migrated by `engine.RestoreBoard` / `Board.RestoreFrom`; that
migration shim is itself slated for removal in v0.4.

The recommended upgrade path is the two-step process documented in
the _Release coordination with `sdkx`_ table above: first swap
imports during the v0.2.x window (no tag boundary crossed), then
`go get -u` once v0.3.0 ships.

## Timeline

- **`sdk/v0.2.0` – `sdk/v0.2.11`** (released): Deprecation notices
  in place across every affected package. No behavioural change.
  Users SHOULD start migrating in advance.
- **`sdkx/v0.2.0` – `sdkx/v0.2.9`** (released, parallel): New
  `sdkx/tool/*` packages land alongside the existing `sdk`
  locations so callers can switch imports at their own schedule,
  before any source-level breakage.
- **`sdk/v0.3.0`** (released): All breaking changes documented
  above land as one cut. Release notes reference this file.
- **`sdkx/v0.3.0`** (follows `sdk/v0.3.0`): Tagged once `sdkx/go.mod`
  is bumped to `github.com/GizClaw/flowcraft/sdk v0.3.0`. Tool
  wrappers under `sdkx/tool/{knowledge,kanban,history}` take
  ownership of the implementations relocated out of `sdk`. Public
  signatures remain identical; downstream code that already swapped
  imports during the v0.2.x window only needs `go get -u`.
- **`sdk/v0.4.0`** (later): Removal of the `MainChannel`
  legacy-key migration shim in `engine.RestoreBoard` /
  `Board.RestoreFrom`. By then all persisted checkpoints should
  have been re-taken with a v0.3+ writer.
