@exellix/ai-tasks 8.4.2 → 8.4.3

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.
Files changed (94) hide show
  1. package/.docs/DOWNSTREAM_ENV.md +42 -0
  2. package/.docs/FEEDBACK_TO_CLIENT_DOWNSTREAM_FIXES.md +64 -0
  3. package/.docs/INTERMEDIATE_STEPS.md +82 -0
  4. package/.docs/activity-structure.md +31 -0
  5. package/.docs/ai-task-ai-scoping-spec.md +338 -0
  6. package/.docs/ai-tasks-model-profile-aliases-7x.md +74 -0
  7. package/.docs/blockers-and-issues.md +346 -0
  8. package/.docs/building-runTask-sdk.md +659 -0
  9. package/.docs/building-skill-execution-orchestrator.md +968 -0
  10. package/.docs/code-used-before/run-task.txt +39 -0
  11. package/.docs/code-used-before/task-executor.ts.old +57 -0
  12. package/.docs/code-used-before/test-run-task.ts.old +42 -0
  13. package/.docs/code-used-before/types.txt +23 -0
  14. package/.docs/env-ready-policy.md +40 -0
  15. package/.docs/flow-io/flow-README.md +76 -0
  16. package/.docs/flow-io/narrix.md +124 -0
  17. package/.docs/flow-io/web-scoping.md +135 -0
  18. package/.docs/flow-io/xynthesis-post.md +154 -0
  19. package/.docs/flow-io/xynthesis-pre.md +181 -0
  20. package/.docs/gap-analysis.md +201 -0
  21. package/.docs/integration-facts-ai-tasks.md +109 -0
  22. package/.docs/investigation/ai-skills.md +170 -0
  23. package/.docs/investigation/external-packages-assignments.md +66 -0
  24. package/.docs/investigation/integration-summary.md +20 -0
  25. package/.docs/investigation/narrix-catalox.md +29 -0
  26. package/.docs/investigation/workplan-close-graph-engine-gaps.md +101 -0
  27. package/.docs/logging-stack.md +30 -0
  28. package/.docs/memory-narrix-adapter-developer-guide.md +402 -0
  29. package/.docs/memory-narrix-adapter-requirements.md +112 -0
  30. package/.docs/narrix-context-consumption-gap.md +184 -0
  31. package/.docs/narrix-context-downstream-report.md +30 -0
  32. package/.docs/narrix-ingest-and-packs-library-spec.md +240 -0
  33. package/.docs/narrix-record-input-current-design.md +48 -0
  34. package/.docs/pacakge.md +48 -0
  35. package/.docs/possible-components/README.md +11 -0
  36. package/.docs/possible-components/integration/README.md +10 -0
  37. package/.docs/possible-components/integration/gaps-when-merging.md +16 -0
  38. package/.docs/possible-components/integration/platform.md +54 -0
  39. package/.docs/possible-components/integration/reintegrate-into-ai-tasks.md +26 -0
  40. package/.docs/possible-components/integration/roadmap-and-checklists.md +54 -0
  41. package/.docs/possible-components/post-component/README.md +18 -0
  42. package/.docs/possible-components/post-component/builder-guide.md +175 -0
  43. package/.docs/possible-components/post-component/gaps-and-artifacts.md +52 -0
  44. package/.docs/possible-components/post-component/handler-audit.md +47 -0
  45. package/.docs/possible-components/post-component/handler-polish.md +41 -0
  46. package/.docs/possible-components/post-component/unified-protocol.md +59 -0
  47. package/.docs/possible-components/pre-component/README.md +22 -0
  48. package/.docs/possible-components/pre-component/builder-guide.md +127 -0
  49. package/.docs/possible-components/pre-component/gaps-and-artifacts.md +35 -0
  50. package/.docs/possible-components/pre-component/handler-ai-scoping.md +45 -0
  51. package/.docs/possible-components/pre-component/handler-narrix-preprocessor.md +49 -0
  52. package/.docs/possible-components/pre-component/handler-narrix-system2.md +35 -0
  53. package/.docs/possible-components/pre-component/handler-synthesized-context.md +65 -0
  54. package/.docs/possible-components/pre-component/handler-web-scope.md +29 -0
  55. package/.docs/possible-components/pre-component/unified-protocol.md +89 -0
  56. package/.docs/prefer-openrouter-routing-policy.md +132 -0
  57. package/.docs/questions-for-ai-skills.md +123 -0
  58. package/.docs/realtime-narrixing-gap-analysis.md +40 -0
  59. package/.docs/realtime-narrixing.md +433 -0
  60. package/.docs/run-context-object.md +32 -0
  61. package/.docs/session-id-usage.md +26 -0
  62. package/.docs/skill-library-spec.md +249 -0
  63. package/.docs/synthesized-context-strategy-spec.md +906 -0
  64. package/.docs/upstream-issue/2026-03-21_woroces-ai-tasks_ISSUE-006_web-scope-question-from-cni-entity.md +46 -0
  65. package/.docs/web-scopper-embed.md +93 -0
  66. package/.docs/xynthesis-wiring-and-io.md +12 -0
  67. package/README.md +15 -13
  68. package/dist/index.d.ts +2 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +1 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/internal/runPostStepLlmCall.d.ts.map +1 -1
  73. package/dist/internal/runPostStepLlmCall.js +4 -2
  74. package/dist/internal/runPostStepLlmCall.js.map +1 -1
  75. package/dist/invocation/resolveProfileInvocationRouting.js +2 -2
  76. package/dist/invocation/resolveProfileInvocationRouting.js.map +1 -1
  77. package/dist/utils/aiProfileModelFormat.d.ts +2 -2
  78. package/dist/utils/aiProfileModelFormat.js +2 -2
  79. package/dist/utils/aiProfilesCatalog.d.ts +16 -0
  80. package/dist/utils/aiProfilesCatalog.d.ts.map +1 -0
  81. package/dist/utils/aiProfilesCatalog.js +23 -0
  82. package/dist/utils/aiProfilesCatalog.js.map +1 -0
  83. package/dist/utils/resolveAiProfileModel.d.ts +2 -2
  84. package/dist/utils/resolveAiProfileModel.d.ts.map +1 -1
  85. package/dist/utils/resolveAiProfileModel.js +5 -5
  86. package/dist/utils/resolveAiProfileModel.js.map +1 -1
  87. package/dist/utils/routeModelConfigSlots.d.ts +3 -1
  88. package/dist/utils/routeModelConfigSlots.d.ts.map +1 -1
  89. package/dist/utils/routeModelConfigSlots.js +2 -1
  90. package/dist/utils/routeModelConfigSlots.js.map +1 -1
  91. package/documenations/upstream-feature-requests/README.md +2 -2
  92. package/documenations/upstream-feature-requests/ai-tasks-wrap-up-after-upstream.md +5 -2
  93. package/documenations/upstream-feature-requests/xynthesis-orchestrator-invoke-contract-4.2.md +2 -2
  94. package/package.json +2 -1
@@ -0,0 +1,184 @@
1
+ # Narrix Context Consumption Gap (produced, but not grounding the final answer)
2
+
3
+ ## Summary
4
+ Narrix is being executed and its structured output is present in runtime memory, but the task execution / prompt-building layer does not reliably consume it as *primary grounding context* for the final answer.
5
+
6
+ The result is a common symptom:
7
+ - Narrix-generated structured facts exist at runtime
8
+ - but the final LLM output behaves like a generic web-based explanation (or otherwise does not strongly reflect the locally grounded facts)
9
+
10
+ This doc explains:
11
+ 1. What is going wrong
12
+ 2. Why it happens (where the handoff mismatch occurs)
13
+ 3. What needs to be done (implementation checklist)
14
+ 4. How to validate the fix (acceptance checks)
15
+
16
+ ---
17
+
18
+ ## What we observed (symptom)
19
+ When Narrix produces structured scoping/discovery data:
20
+ - the data appears in runtime memory (so Narrix "works")
21
+ - but the downstream component that builds the final prompt does not consistently embed that data in the main context block the LLM relies on
22
+
23
+ The practical "acceptance" symptom:
24
+ - Structured local facts are available
25
+ - but the generated answer does not visibly/consistently use those facts as grounding evidence
26
+
27
+ ---
28
+
29
+ ## Root causes (where the mismatch happens)
30
+
31
+ ### 1) Context injection is opt-in (`includeContextInPrompt`)
32
+ In this repository, the executor only attaches a task-level `context` markdown string when:
33
+ - `request.includeContextInPrompt === true`
34
+
35
+ When context is not requested, the executor receives:
36
+ - `context: ""`
37
+
38
+ So even if Narrix output exists in memory, it will not be embedded into the LLM message unless the pipeline enables context generation.
39
+
40
+ Relevant code path:
41
+ - `src/core/task-sdk.ts` (`_executeDirect`): context becomes empty when `includeContextInPrompt` is not true.
42
+
43
+ ### 2) `executionPipeline` + `synthesized-context` can replace the normal context path (`overrideContext`)
44
+ When `executionPipeline` is used, PRE steps may set an `overrideContext` string.
45
+
46
+ If `overrideContext` is set, `_executeDirect` uses that string directly and skips the normal path that would append Narrix context sections.
47
+
48
+ Net effect:
49
+ - Narrix may be present in memory
50
+ - but the specific prompt context used for the main LLM call may not contain the Narrix-derived grounding you expect
51
+
52
+ Relevant code path:
53
+ - `src/core/task-sdk.ts` (execution pipeline: PRE step `synthesized-context`)
54
+ - `src/core/task-sdk.ts` (`_executeDirect`: if `overrideContext !== undefined`, it uses it as-is)
55
+
56
+ ### 3) Narrix attachment vs `taskMemory.narrix` for `synthesized-context`
57
+ There are two different Narrix "output locations" depending on which Narrix flow is used:
58
+
59
+ 1. **Task-level Narrix pre-processor (`request.narrix`)**
60
+ - Output is attached to `executionMemory._narrix` (or `executionMemory[attachToField]`)
61
+ - `_runSynthesizedContextPreStep()` reads `input.executionMemory[attachToField]` where `attachToField` defaults to `"_narrix"`.
62
+
63
+ 2. **`narrix-then-direct` (`executionType: "narrix-then-direct"`)**
64
+ - Output is written into `taskMemory.narrix` (array)
65
+ - It also sets a convenience field on the skill input as `input.narrixContext`
66
+
67
+ **Current behavior (synthesis PRE step):** `_runSynthesizedContextPreStep` builds `narrixAttachment` from `executionMemory[attachToField]` when it has the enriched attachment shape, **otherwise** it **coerces** `taskMemory.narrix` via `coerceTaskMemoryNarrixToAttachment()` (see `src/core/task-sdk.ts`). So synthesis source material can include Narrix from either location. Use an explicit `contextSourcePolicy` (e.g. `memory-web`) when you must **exclude** Narrix from serialized memory while still using web + memory.
68
+
69
+ Relevant code paths:
70
+ - `src/core/task-sdk.ts` (`_runSynthesizedContextPreStep`): `narrixAttachment` resolution + `resolveSourceMaterial`
71
+ - `src/core/task-sdk.ts` (`narrix-then-direct` branch): writes Narrix output to `taskMemory.narrix` and sets `input.narrixContext`
72
+
73
+ ---
74
+
75
+ ## Why this leads to "generic answers"
76
+ LLMs tend to default to the most salient, consistently provided instruction/context structure.
77
+
78
+ If the final `context` string passed to the executor:
79
+ - is empty, or
80
+ - is synthesized from the wrong subset of available information, or
81
+ - contains web-style evidence but not Narrix grounding, or
82
+ - omits the Narrix sections that the prompt / skill was designed to lean on,
83
+
84
+ then the model will still produce a coherent explanation, but it will be less constrained by the locally grounded structured facts.
85
+
86
+ This matches the observed behavior: Narrix data exists, yet output does not strongly reflect it.
87
+
88
+ ---
89
+
90
+ ## What needs to be done (implementation checklist)
91
+ This section is written as "what the fix should accomplish", not as a specific patch.
92
+
93
+ ### 1) Ensure Narrix grounding is embedded whenever Narrix is requested
94
+ Decision: when Narrix is in use, the system should not silently omit the `context` field.
95
+
96
+ Recommended behavior:
97
+ - If a request includes Narrix (either `request.narrix` or `executionType: "narrix-then-direct"`), default to:
98
+ - `includeContextInPrompt: true`
99
+ - Or throw a clear error when `synthesized-context` PRE is configured but context generation is not enabled.
100
+
101
+ Why:
102
+ - Narrix being created without being injected into `enrichedInput.context` is effectively a no-op for grounding.
103
+
104
+ ### 2) Make `synthesized-context` consume Narrix from the correct location(s)
105
+ `synthesized-context` PRE step must treat Narrix output as "available source material" regardless of whether it lives in:
106
+ - `executionMemory._narrix` (task-level pre-processor flow)
107
+ - `taskMemory.narrix` (narrix-then-direct flow)
108
+
109
+ Options:
110
+ - Convert `taskMemory.narrix` into the `_narrix`-shaped attachment used by `resolveSourceMaterial()`
111
+ - Or extend `resolveSourceMaterial()` / serialization so synthesis can consume `taskMemory.narrix` directly
112
+
113
+ Why:
114
+ - Today, synthesized-context may be blind to narrix-then-direct's canonical output location.
115
+
116
+ ### 3) Prevent `overrideContext` from accidentally dropping Narrix sections
117
+ When `overrideContext` is used, it should still include Narrix grounding sections (or Narrix-serialized source material) when Narrix is available.
118
+
119
+ Two non-exclusive strategies:
120
+ - Synthesis must embed Narrix source material (fix #2), so `overrideContext` naturally contains it
121
+ - Or post-process `overrideContext` to append the Narrix grounding markdown section when Narrix exists
122
+
123
+ Why:
124
+ - Avoids a failure mode where Narrix exists in memory, but the PRE step's override ignores it.
125
+
126
+ ### 4) Clarify the template contract (do not rely on `{{narrix}}` tokens)
127
+ In this repo, the executor passes Narrix grounding via:
128
+ - `enrichedInput.context` (generated markdown string)
129
+ - and for `narrix-then-direct`, also via `input.narrixContext` convenience field
130
+
131
+ Therefore, adding `{{narrix}}` tokens to templates will not automatically help unless templates are explicitly wired to the actual fields this SDK populates.
132
+
133
+ Recommended action:
134
+ - Make the skill/prompt contract reference the real fields this SDK provides:
135
+ - `context` (main visible grounding block)
136
+ - `input.narrixContext` (for `narrix-then-direct`)
137
+ - `taskMemory.narrix` (if a skill reads from memory directly)
138
+
139
+ ---
140
+
141
+ ## Acceptance checks (how to validate the fix)
142
+
143
+ ### A. Context string must include Narrix-derived content
144
+ For a request where Narrix output is expected to be available:
145
+ - the `enrichedInput.context` passed to the executor must contain Narrix grounding markers, such as:
146
+ - `## Scoping and discovery` (for `_narrix`-attachment flow), or
147
+ - `## Narrix Scoping` (for `taskMemory.narrix` flow)
148
+
149
+ At minimum:
150
+ - the context must be non-empty when Narrix is requested
151
+
152
+ ### B. Pipeline with `synthesized-context` must not lose Narrix grounding
153
+ When using `executionPipeline` with a PRE step of type `synthesized-context`:
154
+ - the synthesized context returned by the PRE step must be generated from Narrix output (from the correct location)
155
+ - and the resulting main LLM call must receive a `context` that reflects that grounding
156
+
157
+ ### C. "No Narrix requested" should behave exactly as today
158
+ Regression guardrails:
159
+ - if Narrix is not requested, context behavior should remain unchanged (default empty or context-generator behavior)
160
+ - no accidental placeholder injection should appear
161
+
162
+ ---
163
+
164
+ ## Debug / triage checklist for callers
165
+ When investigating a live case:
166
+ 1. Confirm Narrix flow type:
167
+ - task-level pre-processor: `request.narrix`
168
+ - narrix-then-direct: `executionType: "narrix-then-direct"` + `narrixInput`
169
+ 2. Confirm pipeline usage:
170
+ - is `executionPipeline` set?
171
+ - is there a PRE step of type `synthesized-context`?
172
+ 3. Confirm context opt-in:
173
+ - is `includeContextInPrompt: true` in the final request?
174
+ 4. Inspect the constructed executor input:
175
+ - verify `enrichedInput.context` is non-empty and contains Narrix grounding markers
176
+ - verify `input.narrixContext` exists for narrix-then-direct
177
+
178
+ ---
179
+
180
+ ## Related references
181
+ - `.docs/narrix-context-downstream-report.md` (Task context contract and how ai-tasks controls `context`)
182
+ - `.docs/synthesized-context-strategy-spec.md` (synthesized-context design and constraints)
183
+ - `src/core/task-sdk.ts` (all relevant wiring: context generation, pipeline PRE override, and narrix-then-direct / narrix pre-processor injection)
184
+
@@ -0,0 +1,30 @@
1
+ # Task context in LLM messages
2
+
3
+ All fixes for task context are implemented **here (ai-tasks)**. This document describes what we send and how we control it.
4
+
5
+ ---
6
+
7
+ ## What ai-tasks sends
8
+
9
+ - Ai-tasks passes an **enriched input** to the executor with a **`context`** field: a single string (markdown) that may contain task-level context for the LLM.
10
+ - **Context is opt-in.** We add context only when `request.includeContextInPrompt === true`. By default we send no context (empty string), so the LLM gets system + user only unless the step explicitly requests context.
11
+ - **NARRIX is the source of scoping/discovery context** in the LLM prompt for this flow. When NARRIX is in play and context is requested, ai-tasks builds context only from NARRIX (`buildNarrixPreProcessorContextMarkdown`): a **"## Scoping and discovery"** section when there is real content (signals, stories), or nothing when empty. We do not pass _narrix to the context generator (xontext), so the context map cannot emit the "Narrix Pre-Processor" placeholder; placeholder emission is avoided and NARRIX context is added only in ai-tasks.
12
+ - When NARRIX is not in play and context is requested, context comes from the context generator plus any `taskMemory.narrix` section. The text uses neutral headings only; no internal product or system names appear in the context string.
13
+
14
+ ---
15
+
16
+ ## Summary (all fixes here)
17
+
18
+ | Item | Action |
19
+ |------|--------|
20
+ | What is in context | When requested: NARRIX in play → only "## Scoping and discovery" from _narrix (or empty); NARRIX not in play → context generator + taskMemory.narrix. |
21
+ | Opt-in only | Context is added only when `includeContextInPrompt === true`; default is no context. |
22
+ | No placeholder | We do not use the context generator for context when NARRIX is in play, so the "Narrix Pre-Processor" placeholder is never emitted; NARRIX context is built only in ai-tasks. |
23
+
24
+ ---
25
+
26
+ ## References (for implementors)
27
+
28
+ - **Contract:** `enrichedInput.context` is a markdown string. It is non-empty only when `includeContextInPrompt === true`. When NARRIX is in play, it contains at most a "## Scoping and discovery" subsection from _narrix (or is empty). Placeholder emission is controlled by ai-tasks (we do not pass _narrix to the context generator when NARRIX is in play).
29
+ - **Opt-in:** `request.includeContextInPrompt === true` is required for any context to be sent; default is no context.
30
+ - **No empty/placeholder:** When NARRIX is in play we build context only from NARRIX in ai-tasks; we never add the "Narrix Pre-Processor" placeholder or an empty block.
@@ -0,0 +1,240 @@
1
+ # Narrix Ingest, Packs-Library, and Related Components — Low-Level Spec for Planning
2
+
3
+ This document describes **`@narrices/narrix-ingest`**, **`@narrices/narrix-packs-library`**, and related ai-tasks wiring so you can plan a component that decides **what and whether to search the web** (or other sources) to obtain the **complete story / information** needed for a Narrix run.
4
+
5
+ **Audience:** Low-level spec for new component planning (e.g. “pre-narrix info gatherer” or “gap-driven web search”).
6
+ **Scope:** Contract and data flow only; implementation details of ingest/packs-library live in those packages.
7
+
8
+ ---
9
+
10
+ ## 1. Overview: From Input to “Complete Story”
11
+
12
+ - **Narrix** turns raw input (record / text / docs / chat) into **stories** and **signals** via: **ingest** (input → CNI) and **runner** (CNI + datasetId → entity, stories, signals, passes).
13
+ - **“Complete story”** in this codebase means: a successful Narrix run (`ok: true`) with **stories** and **signals** produced and, optionally, no **gaps** (no missing mapping, schema, or unknown dataset).
14
+ - To know **whether** you have enough info to run Narrix (or need to search the web first), you need:
15
+ 1. **What ingest needs** — per-medium input shapes and where they come from.
16
+ 2. **What packs-library provides** — which datasets exist, which schema/roleMap each needs, and whether routing/schema can be “patched” at runtime (smart mode).
17
+ 3. **What “incomplete” looks like** — gap detection (errors, unknown dataset, missing schema, etc.) and how that can drive a decision to fetch more info.
18
+
19
+ Sections below specify each piece so a new component can rely on them.
20
+
21
+ ---
22
+
23
+ ## 2. `@narrices/narrix-ingest` (narrix-ingest)
24
+
25
+ ### 2.1 Role
26
+
27
+ - **Ingest** converts **raw input** into a single internal format (**CNI**) that the **runner** consumes.
28
+ - ai-tasks does **not** implement ingest; it uses the default export from `@narrices/narrix-ingest` and calls `ingest.toCni({ kind, input })`.
29
+ - Adapter selection (records vs text vs docs vs chat) is **ingest’s responsibility**; ai-tasks only passes `kind` and the corresponding `input` shape.
30
+
31
+ ### 2.2 Where It’s Used in ai-tasks
32
+
33
+ | File | Usage |
34
+ |------|--------|
35
+ | `src/narrix/narrixClient.ts` | Imports `defaultIngest` and type `NarrixIngest`; exposes `getNarrixClient()` → `{ ingest, runner }`. |
36
+ | `src/narrix/runNarrixForRecord.ts` | `ingest.toCni({ kind: "records", input: { record, recordType, roleMap } })`. |
37
+ | `src/narrix/runNarrixForText.ts` | `ingest.toCni({ kind: "text", input: { text, sourceMeta, meta } })`. |
38
+ | `src/narrix/runNarrixForDocs.ts` | `ingest.toCni({ kind: "docs", input: { pages, docId, title, sourceMeta } })`. |
39
+ | `src/narrix/runNarrixForChat.ts` | `ingest.toCni({ kind: "chat", input: { messages, threadId, title, participants, sourceMeta } })`. |
40
+
41
+ ### 2.3 Required API (Consumer Contract)
42
+
43
+ - **Export:** `defaultIngest` (or equivalent that yields a `NarrixIngest` instance).
44
+ - **Type:** `NarrixIngest`.
45
+ - **Method:** `ingest.toCni(options)` where `options` has:
46
+ - `kind`: `"records"` | `"text"` | `"docs"` | `"chat"`
47
+ - `input`: object shape below for that `kind`.
48
+
49
+ **Return:**
50
+
51
+ - **Success:** `{ cni: <opaque> }` (no `error: true`). ai-tasks passes `cni` to `runner.run({ cni, datasetId, sourceMeta })`.
52
+ - **Error:** `{ error: true, code: string, message: string }`. ai-tasks surfaces `code` and `message` in the run output.
53
+
54
+ ### 2.4 Exact `toCni` Call Shapes (What Ingest Needs)
55
+
56
+ These are the **minimum** inputs ingest must receive to produce CNI. If your component needs to “have the complete info” before calling Narrix, it must ensure these are available (or know that they will be filled by someone else).
57
+
58
+ | `kind` | `input` shape | Notes |
59
+ |--------|----------------|--------|
60
+ | **records** | `{ record: Record<string, unknown>; recordType: string; roleMap: RoleMap }` | `recordType` = entityKind from pack. `roleMap` comes from packs-library (schema → roleMap). |
61
+ | **text** | `{ text: string; sourceMeta: { kind: "text"; datasetId: string }; meta?: Record<string, unknown> }` | Free-form text + datasetId. |
62
+ | **docs** | `{ pages: NarrixDocPage[]; docId?: string; title?: string; sourceMeta: { kind: "doc"; datasetId: string } }` | `pages[].text` (and optional pageId, pageNumber, index, mime, meta). |
63
+ | **chat** | `{ messages: NarrixChatMessage[]; threadId?: string; title?: string; participants?: string[]; sourceMeta: { kind: "chat"; datasetId: string } }` | Messages with `role`, `text`, optional id/ts/authorId/meta. |
64
+
65
+ For **records**, the only way to get `roleMap` and `recordType` in ai-tasks is via **packs-library** (see next section). So “do I have enough to run record ingest?” depends on: (1) having a `record` object, (2) having a `datasetId` that resolves to a pack/processor/schema in packs-library.
66
+
67
+ ### 2.5 Implications for “Complete Info” / Web Search
68
+
69
+ - **Records:** You need the **record** payload and a **datasetId** that the packs-library knows (pack + processor + schema). If the dataset is unknown or schema is missing, ingest or the next step (resolveRoleMap / runner) will fail — that’s when gap handling or a “fetch more info” step (e.g. web search to discover or enrich data) can be relevant.
70
+ - **Text/Docs/Chat:** You need the **content** (text, pages, messages) and a **datasetId**. Same idea: if the dataset isn’t in the registry, resolution fails; otherwise ingest only needs that content + datasetId in sourceMeta.
71
+
72
+ So for planning a “what/if to search” component:
73
+
74
+ - **Ingest itself** does not define “complete story”; it only requires the above shapes. “Completeness” is determined by: (a) packs-library having routing + schema for the dataset, and (b) runner succeeding and optionally (c) no gaps in the sense of System-2 (see Section 5).
75
+
76
+ ---
77
+
78
+ ## 3. `@narrices/narrix-packs-library` (narrix-packs-library)
79
+
80
+ ### 3.1 Role
81
+
82
+ - **Packs-library** provides the **registry of Narrix packs** and helpers to resolve **datasetId → pack, processor, entityKind, record schema**, and **roleMap**.
83
+ - It also provides **smart-mode APIs** (when available): Xronox-backed “remember” operations so routing and schema can be updated at runtime (System-2).
84
+
85
+ ### 3.2 Where It’s Used in ai-tasks
86
+
87
+ | File | Usage |
88
+ |------|--------|
89
+ | `src/narrix/resolveRoleMap.ts` | `PACK_REGISTRY`, `findPackByDatasetId(datasetId, registry)`, `getRecordSchema(packId, entityKind, registry)`; `schemaToRoleMap(schema)` from `@narrices/narrix-adapter-records`. |
90
+ | `src/narrix/system2/smartInit.ts` | Dynamic import: `createXronoxFromEnv`, `configurePacksLibrarySmartMode` (smart mode init). |
91
+ | `src/narrix/system2/applyPatchPlan.ts` | Dynamic import: `rememberDatasetPackRouting`, `rememberDatasetProcessorRouting`, `rememberRecordSchemaOverride` (apply System-2 patch plan). |
92
+
93
+ ### 3.3 Static API (Compile-Time Exports)
94
+
95
+ - **`PACK_REGISTRY`** — `Record<string, NarrixPack>`. All packs known to the library (default registry).
96
+ - **`findPackByDatasetId(datasetId, registry?)`** — Returns the pack that has a processor whose `route.datasetIds` includes `datasetId`, or undefined.
97
+ - **`getRecordSchema(packId, entityKind, registry?)`** — Returns the record schema for that pack/entityKind, or undefined.
98
+
99
+ From these, ai-tasks derives:
100
+
101
+ - **Pack id** and **processor** for the given `datasetId`.
102
+ - **entityKind** (from processor’s `entityKind` or `id`).
103
+ - **roleMap** = `schemaToRoleMap(schema)` (adapter-records); required for `toCni({ kind: "records", input: { record, recordType, roleMap } })`.
104
+
105
+ If `findPackByDatasetId` returns nothing → “pack not found for datasetId” (unknown dataset). If `getRecordSchema` returns nothing → “record schema missing”. Both are **gaps** that can trigger System-2 or a “need more info” decision.
106
+
107
+ ### 3.4 Pack/Processor Shape (What ai-tasks Assumes)
108
+
109
+ - **Pack:** has `id` and `processors` (array).
110
+ - **Processor:** has `id`, `route?.datasetIds` (string[]), `entityKind` (or fallback to `id`). The processor that “handles” a datasetId is the one whose `route.datasetIds` includes that datasetId.
111
+ - **Schema:** shape is opaque to ai-tasks; only that `getRecordSchema(packId, entityKind, registry)` returns something that `schemaToRoleMap` accepts.
112
+
113
+ ### 3.5 Smart-Mode APIs (Runtime / Optional)
114
+
115
+ These may not be present in all versions; ai-tasks uses dynamic import and runtime checks.
116
+
117
+ - **Init:** `createXronoxFromEnv()`, `configurePacksLibrarySmartMode({ xronox, enableWrites, role })`.
118
+ - **Remember (used by System-2 applyPatchPlan):**
119
+ - `rememberDatasetPackRouting(scope, { datasetId, packId })`
120
+ - `rememberDatasetProcessorRouting(scope, { datasetId, packId, processorId, entityKind })`
121
+ - `rememberRecordSchemaOverride(scope, { packId, entityKind, schemaOverride, mergeStrategy })`
122
+
123
+ **Scope** is `NarrixSmartScope`: `{ scopeType: "domain" | "agent", key: string }`. So “what/if to search” can be independent of smart mode; smart mode is about **persisting** fixes (new dataset routing, schema overrides) so the next run has “complete” routing/schema without searching again.
124
+
125
+ ### 3.6 Implications for “Complete Info” / Web Search
126
+
127
+ - To know **whether** a given `datasetId` can be run:
128
+ - Call `findPackByDatasetId(datasetId, PACK_REGISTRY)` (or a custom registry). If null → **unknown dataset**; a planner might decide to search the web (or ask a user) to map this dataset to a pack or to create a new pack entry.
129
+ - To know **what** structure a record must have for that dataset:
130
+ - After resolving the pack/processor, call `getRecordSchema(packId, entityKind, registry)`. If null → **missing schema**; a planner might search for schema docs or use LLM to infer a schema and then use System-2 remember (if available) or report that schema is missing.
131
+ - **Stories/signals** semantics: produced by the **runner**, not packs-library. Packs-library only provides **routing and schema** so that ingest and runner can run. “Complete story” in the sense of “we have stories/signals” is an **output** of the pipeline; “complete info to run” is “we have datasetId + pack + schema + input payload”.
132
+
133
+ ---
134
+
135
+ ## 4. Related Components in ai-tasks (Relevant for “Complete Story” and Search)
136
+
137
+ ### 4.1 resolveRoleMap (`src/narrix/resolveRoleMap.ts`)
138
+
139
+ - **Input:** `datasetId`, optional `registry` (default `PACK_REGISTRY`).
140
+ - **Output:** `{ roleMap, packId, entityKind }` or throws if pack not found, processor not found, or schema missing.
141
+ - **Use:** All four media runners call this before `ingest.toCni` (for records they pass `roleMap` and `entityKind` as `recordType`; for text/docs/chat they still resolve pack/entityKind for runner and meta).
142
+ - **For planning:** A component that “checks if we can run” can call `resolveRoleMapForDataset(datasetId)` and catch errors to distinguish “unknown dataset” vs “missing schema” vs “processor not found”.
143
+
144
+ ### 4.2 narrixClient (`src/narrix/narrixClient.ts`)
145
+
146
+ - **`getNarrixClient(): Promise<{ ingest, runner }>`** — Singleton. Ingest = `defaultIngest`; runner = `createRunner({ runtime: { onProcessorNotMatched: "attachError", onMissingMapping: "attachError", deterministicSort: true } })`.
147
+ - Use this to get `ingest` and `runner` when you actually run the pipeline.
148
+
149
+ ### 4.3 Runner Contract (`@narrices/narrix-runner`)
150
+
151
+ - **`runner.run({ cni, datasetId, sourceMeta })`** → Promise of `{ entity, signals, stories, passes, meta }`.
152
+ - **entity:** `{ entityKind, entityId?, entityKey }`.
153
+ - **stories:** array of objects (e.g. with `narrativeTypeId`); “story” in the product sense.
154
+ - **signals:** array of objects (e.g. with `code`).
155
+ - **meta:** includes `runId`, `producedAt`, `processorId`, `packVersion`, etc.
156
+ - Success/failure: runner may throw or return; ai-tasks also interprets `output.ok: false` and meta.errors/diagnostics for gap detection.
157
+
158
+ ### 4.4 System-2: Gaps and Patch Plan (`src/narrix/system2/`)
159
+
160
+ - **detectGaps(output)** — Pure. Considers `output.ok === false` and scans meta.errors, diagnostics, passes for keywords:
161
+ - **processorNotMatched**, **missingMapping**, **missingSchema**, **unknownDataset** (and generic “error”).
162
+ - **GapHints:** `{ processorNotMatched?, missingMapping?, missingSchema?, unknownDataset?, details? }`.
163
+ - **buildSystem2Input(input, result, gapHints)** — Builds a string for the LLM that produces a **PatchPlan** (actions: rememberDatasetPackRouting, rememberDatasetProcessorRouting, rememberRecordSchemaOverride; rerun: boolean).
164
+ - **applyPatchPlan(plan, smartScope, enableWrites)** — Calls packs-library remember* APIs; if no scope or writes disabled, actions are skipped.
165
+ - **runWithSystem2** — Runs System-1, then if gaps (or ALWAYS mode), calls LLM for patch plan, applies via packs-library, optionally reruns.
166
+
167
+ So “incomplete” in code is explicitly: **hasGap** from `detectGaps` (error, processorNotMatched, missingMapping, missingSchema, unknownDataset). A “what/if to search” component could use the same hints: e.g. if **unknownDataset** → maybe search for which pack/dataset to use; if **missingSchema** → maybe search for schema or use LLM to propose one.
168
+
169
+ ### 4.5 Types (Unified Input / Output)
170
+
171
+ - **NarrixRunInput** — Discriminated by `medium`: `record` | `text` | `docs` | `chat`, plus `datasetId` and the corresponding payload (record, text, document, thread).
172
+ - **NarrixRunOutput** — Success: `ok: true`, `entity`, `signals`, `stories`, `passes`, `meta`. Failure: `ok: false`, `error`, `message`, `meta?`.
173
+ - **Stories** are the main “narrative” output; they carry `narrativeTypeId` in scoping/filtering. So “complete story” can mean: we have at least one story, or we have stories for expected narrativeTypeIds (depending on product requirements).
174
+
175
+ ---
176
+
177
+ ## 5. When to Search the Web (or Gather More Info) — Spec for a New Component
178
+
179
+ Using the above, here is a **low-level spec** for a component that decides **what and if to search** to get the complete story/info.
180
+
181
+ ### 5.1 Prerequisites You Can Check (Without Running Narrix)
182
+
183
+ 1. **Dataset known to packs-library**
184
+ - Call `findPackByDatasetId(datasetId, PACK_REGISTRY)` (or resolveRoleMap and catch).
185
+ - If **no pack** → **unknown dataset**. Options: (a) search the web (or docs) for “which pack/dataset handles X”, (b) ask user, (c) let System-2 try to remember a new routing (if smart mode and LLM suggest one).
186
+ 2. **Schema available for record runs**
187
+ - For `medium === "record"`, after resolving pack/processor, call `getRecordSchema(packId, entityKind, registry)`.
188
+ - If **no schema** → **missing schema**. Options: (a) search for schema/docs for that pack or entityKind, (b) use LLM to propose a schema and then rememberRecordSchemaOverride (if smart mode), (c) fail with a clear “schema missing” for this dataset.
189
+ 3. **Input payload present**
190
+ - Records: need a `record` object (and optionally recordId). Text: need `text`. Docs: need `document.pages`. Chat: need `thread.messages`.
191
+ - If payload is **empty or clearly placeholder** → might decide to “search” or “enrich” (e.g. fetch from API or web) before calling Narrix.
192
+
193
+ ### 5.2 After a Narrix Run (Gap-Driven)
194
+
195
+ 1. **Run Narrix** (ingest → runner) and get **NarrixRunOutput**.
196
+ 2. **Run `detectGaps(output)`** to get `hasGap`, `reasons`, `gapHints`.
197
+ 3. **If hasGap:**
198
+ - **unknownDataset** → Same as 5.1: decide whether to search for pack/dataset mapping or to call System-2 (if enabled) to remember new routing.
199
+ - **missingSchema** → Search for schema or use LLM + rememberRecordSchemaOverride.
200
+ - **processorNotMatched** / **missingMapping** → May need to search for mapping rules, or let System-2 propose rememberDatasetPackRouting / rememberDatasetProcessorRouting.
201
+ 4. **If !hasGap but “story” is empty:** Product decision: is “no stories” acceptable or do we need to “enrich” input (e.g. web search) and re-run? This is not currently a “gap” in code; a new component could define a policy (e.g. “if stories.length === 0 and policy says require-stories, then try to enrich and re-run”).
202
+
203
+ ### 5.3 What “Complete Story / Info” Means (Checklist for Spec)
204
+
205
+ - **Enough to run ingest:** You have the correct `kind` and `input` shape for that medium (see Section 2.4), and for records you have `roleMap` and `recordType` (from packs-library).
206
+ - **Enough to run runner:** You have `cni` from ingest and `datasetId` (and optional sourceMeta).
207
+ - **Enough to consider “complete”:** Either (a) run succeeded and you accept the stories/signals you got, or (b) run failed with gaps and you have a policy: e.g. “on unknownDataset we search once for pack mapping”, “on missingSchema we search for schema or call System-2”, “on empty stories we optionally enrich and re-run”.
208
+
209
+ A new component can implement:
210
+ - **Pre-run:** “Resolve dataset + schema; if missing, search (web/docs/LLM) and optionally update registry or smart overlay.”
211
+ - **Post-run:** “If detectGaps says hasGap, classify by gapHints and trigger the right search or System-2 path; optionally retry after applyPatchPlan.”
212
+
213
+ ---
214
+
215
+ ## 6. Quick Reference: Packages and Files
216
+
217
+ | Concern | Package / File | Key exports / functions |
218
+ |--------|----------------|---------------------------|
219
+ | Ingest | `@narrices/narrix-ingest` | `defaultIngest`, `NarrixIngest`, `toCni({ kind, input })` |
220
+ | Packs registry | `@narrices/narrix-packs-library` | `PACK_REGISTRY`, `findPackByDatasetId`, `getRecordSchema` |
221
+ | RoleMap for records | `@narrices/narrix-adapter-records` (via resolveRoleMap) | `schemaToRoleMap(schema)` |
222
+ | Resolve dataset → pack/schema | `src/narrix/resolveRoleMap.ts` | `resolveRoleMapForDataset(datasetId, registry?)` |
223
+ | Client | `src/narrix/narrixClient.ts` | `getNarrixClient()` → `{ ingest, runner }` |
224
+ | Runner | `@narrices/narrix-runner` | `createRunner(options)`, `runner.run({ cni, datasetId, sourceMeta })` |
225
+ | Gaps | `src/narrix/system2/detectGaps.ts` | `detectGaps(output)` → `{ hasGap, reasons, gapHints }` |
226
+ | System-2 input for LLM | `src/narrix/system2/buildSystem2Input.ts` | `buildSystem2Input(input, result, gapHints)` |
227
+ | Patch plan | `src/narrix/system2/patchPlan.ts` | `validatePatchPlan(raw)`, types `PatchPlan`, `PatchPlanAction` |
228
+ | Apply patch | `src/narrix/system2/applyPatchPlan.ts` | `applyPatchPlan(plan, smartScope, enableWrites)` |
229
+ | Smart init | `src/narrix/system2/smartInit.ts` | `ensureSmartModeInitialized({ enableWrites, role })` |
230
+ | Scope for smart writes | `src/narrix/system2/resolveSmartScope.ts` | `resolveSmartScope(system2Options, ctx)` → `NarrixSmartScope | null` |
231
+ | Types | `src/narrix/types.ts` | `NarrixRunInput`, `NarrixRunOutput`, `NarrixSystem2Options`, etc. |
232
+
233
+ ---
234
+
235
+ ## 7. Summary for New Component Planning
236
+
237
+ - **narrix-ingest** defines **what input shape** is needed per medium to produce CNI; it does not define “complete story” — that’s runner output and gap semantics.
238
+ - **narrix-packs-library** defines **which datasets exist**, **how to get roleMap/schema** for records, and **how to persist new routing/schema** (smart mode) so future runs have “complete” config.
239
+ - **Gap detection** (unknown dataset, missing schema, processor not matched, missing mapping) is the **signal** for “we don’t have the complete info yet”; a new component can use it to decide **what** to search (e.g. pack name, schema, mapping) and **whether** to search (e.g. only on unknownDataset, or also on empty stories by policy).
240
+ - This doc is sufficient to specify a **low-level component** that: (1) before run: checks dataset/schema and optionally triggers web (or other) search to fill gaps; (2) after run: uses `detectGaps` and optionally System-2 or search to fix gaps and retry; (3) defines “complete story” as run success + optional story/signal presence policy.
@@ -0,0 +1,48 @@
1
+ # NARRIX record input: how it works today
2
+
3
+ This document describes **where ai-tasks gets the record** (or more generally the input) that it passes to NARRIX when running it. It is a snapshot of the current design so it can be compared with a desired design later.
4
+
5
+ ---
6
+
7
+ ## Two ways NARRIX is run
8
+
9
+ 1. **Task-level pre-processor** — When `request.narrix` is set (graph/node config), ai-tasks runs NARRIX before the task and injects the result into `executionMemory` (and `jobMemory`). The input to NARRIX is **always a single record** (medium `"record"`). The record is **resolved from the request** using a fixed priority list; there is no separate “pointer” or “record ref” from memory.
10
+
11
+ 2. **NARRIX_THEN_DIRECT** — When `executionType === "narrix-then-direct"`, the client must pass `request.narrixInput`. That can be a full `NarrixRunInput` (record, text, docs, or chat) **inline**, or `{ $path: "jobMemory.<path>" }` so the value is read from `jobMemory` at that path. So for this path, the “where” is explicit: either the request body or jobMemory at the given path.
12
+
13
+ The design issue discussed here applies mainly to **path 1 (pre-processor)**: the record is not pointed to by memory; it is **scavenged** from several possible locations.
14
+
15
+ ---
16
+
17
+ ## Pre-processor: where the record comes from today
18
+
19
+ The pre-processor **never** uses `request.narrixInput`. It builds a `NarrixRunInputRecord` itself:
20
+
21
+ - `datasetId` comes from `request.narrix.datasetId`.
22
+ - `record` and `sourceMeta` come from **resolvePreProcessorInput(request)**.
23
+
24
+ That function ([`src/narrix/resolvePreProcessorInput.ts`](../src/narrix/resolvePreProcessorInput.ts)) looks for a **raw record** in this **strict priority order**:
25
+
26
+ | Order | Source | Meaning |
27
+ |-------|--------|--------|
28
+ | 1 | `executionMemory.input.raw` | Current input’s `raw` field in execution memory. |
29
+ | 2 | `executionMemory.inputs.<first key>.raw` | First key in a keyed map of inputs; that entry’s `raw`. |
30
+ | 3 | `jobMemory.record` | Job-level “record” field. |
31
+ | 4 | `jobMemory.currentRecord` | Job-level “current record” field. |
32
+ | 5 | `request.input.record` | Task input’s `record` field. |
33
+ | 6 | `request.input.raw` | Task input’s `raw` field. |
34
+
35
+ **sourceMeta** is taken from (in order): `executionMemory.input.sourceMeta`, else `jobMemory.sourceMeta`, else `{}`.
36
+
37
+ If none of these locations holds a valid record (object), the function returns `null` and the SDK throws: *"NARRIX pre-processor: raw record not found. Set executionMemory.input.raw, executionMemory.inputs.<key>.raw, jobMemory.record, jobMemory.currentRecord, or input.record / input.raw."*
38
+
39
+ ---
40
+
41
+ ## What’s wrong with this design (as identified)
42
+
43
+ - **No single contract.** The runner/caller does not know which field “is” the record; ai-tasks just checks a fixed list. Different callers may set different places, and behavior depends on **order** (e.g. if both `jobMemory.currentRecord` and `request.input.record` are set, the former wins because it’s checked first).
44
+ - **Memory does not “point” to the record.** There is no agreed convention like “the record for this task is always at jobMemory.currentRecord” or “executionMemory.input.raw is the canonical record.” The code only “finds” a record in one of several slots.
45
+ - **Pre-processor vs NARRIX_THEN_DIRECT are inconsistent.** For NARRIX_THEN_DIRECT, the input is explicit (`narrixInput` or `$path`). For the pre-processor, the input is implicit (whatever is found by the resolution order above).
46
+ - **Keying is arbitrary.** Using “first key” of `executionMemory.inputs` is undefined if key order matters; the contract is unclear.
47
+
48
+ This document does not define how it *should* work; it only records how it *is* done today so that a better design can be specified and implemented later.
@@ -0,0 +1,48 @@
1
+ **don’t use CommonJS** 🙂
2
+
3
+ ### Use **ESM only**
4
+
5
+ ```json
6
+ {
7
+ "compilerOptions": {
8
+ "target": "ES2022",
9
+ "module": "NodeNext",
10
+ "moduleResolution": "NodeNext"
11
+ }
12
+ }
13
+ ```
14
+
15
+ ### Why this is the *right* choice **now**
16
+
17
+ * **Native ESM everywhere** (Node 18/20+, Bun, Deno, modern bundlers)
18
+ * Clean `import` / `export`, no interop hacks
19
+ * Future-proof for:
20
+
21
+ * tree-shaking
22
+ * edge / workers
23
+ * package `exports`
24
+ * Matches how *new* libraries are written in 2025–2026
25
+
26
+ ### What *not* to do
27
+
28
+ * ❌ `"module": "commonjs"` → legacy, friction, dead-end
29
+ * ❌ `"module": "ES2020"` without `NodeNext` → subtle resolution bugs with Node ESM
30
+
31
+ ### Minimal `package.json` you should use
32
+
33
+ ```json
34
+ {
35
+ "type": "module",
36
+ "exports": {
37
+ ".": "./dist/index.js"
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### One important rule (don’t trip on this)
43
+
44
+ * Use **explicit file extensions** in imports:
45
+
46
+ ```ts
47
+ import { x } from "./utils.js";
48
+ ```
@@ -0,0 +1,11 @@
1
+ # Possible components (specs)
2
+
3
+ Architecture-only documentation for extracting **Pre-Task** and **Post-Task** components and **re-integrating** them into `ai-tasks`.
4
+
5
+ | Folder | Use when you… |
6
+ |--------|----------------|
7
+ | [`pre-component/`](pre-component/README.md) | Build or maintain everything that runs **before MAIN**. **Implementation entry:** [`pre-component/builder-guide.md`](pre-component/builder-guide.md). |
8
+ | [`post-component/`](post-component/README.md) | Build or maintain everything that runs **after MAIN**. **Implementation entry:** [`post-component/builder-guide.md`](post-component/builder-guide.md). |
9
+ | [`integration/`](integration/README.md) | Shared platform (**single source**), phased checklist, merge-back into `ai-tasks`. |
10
+
11
+ **Rule:** Shared rules (aifunctions-js, Activix, identity, Xynthesis layering) live **only** in [`integration/platform.md`](integration/platform.md). Pre/post **builder guides** summarize obligations and link there for full detail—no second full copy of platform prose.
@@ -0,0 +1,10 @@
1
+ # Integration — shared platform & merge-back into `ai-tasks`
2
+
3
+ Use this folder for **shared** concerns. For **building** each component, start with [`../pre-component/builder-guide.md`](../pre-component/builder-guide.md) and [`../post-component/builder-guide.md`](../post-component/builder-guide.md) (complete deps, env, files, acceptance).
4
+
5
+ | Doc | Purpose |
6
+ |-----|---------|
7
+ | [`platform.md`](platform.md) | **Only** place for aifunctions-js, Activix, `identity`, Xynthesis layering (no duplicate in pre/post). |
8
+ | [`roadmap-and-checklists.md`](roadmap-and-checklists.md) | Phased A–D checklist + pre layering diagram. |
9
+ | [`reintegrate-into-ai-tasks.md`](reintegrate-into-ai-tasks.md) | Where to wire extracted packages back into this repo. |
10
+ | [`gaps-when-merging.md`](gaps-when-merging.md) | Versioning, barrels, DI, docs to update when merging. |
@@ -0,0 +1,16 @@
1
+ # Gaps when merging packages back into `ai-tasks`
2
+
3
+ Items that often bite during **integration**, distinct from per-component template/code lists.
4
+
5
+ | Gap | Mitigation |
6
+ |-----|------------|
7
+ | **Version skew** | Pin `@athenices/xynthesis`, `aifunctions-js`, `@woroces/ai-skills`, `@narrices/*` to versions tested together. |
8
+ | **Barrel exports** | Update `src/index.ts` / `package.json` `exports` if pre/post move; avoid duplicate symbols. |
9
+ | **Singleton globals** | `setScopingGateway`, `setSynthesisInvoker`, web scoper singleton — document thread-safety or replace with instance DI. |
10
+ | **Cwd-relative templates** | Post templates use `process.cwd()`; in monorepo tests, set `AUDIT_TEMPLATES_PATH` / `POLISH_TEMPLATES_PATH` to repo root. |
11
+ | **dist vs src** | If consumers import `dist-test`, align build pipeline so specs match published paths. |
12
+ | **Documentation drift** | Update this tree if `task-sdk` flow changes; keep **one** platform doc ([`platform.md`](platform.md)). |
13
+
14
+ ## Optional shared library
15
+
16
+ Consider a tiny **`@woroces/ai-task-platform`** (or similar) owning: Activix+identity helper, `runLlmTextCall` wrapper, and SynthesisInvoker factory — imported by both pre and post packages and by `ai-tasks`.