Microsoft Agent Framework python-1.18.0 Adoption Review

Mechanism-level review of the tool-loop duration bound (#7772) and the shared vector-store abstraction (#8014/#8115 plus connectors) from Microsoft Agent Framework python-1.18.0 against the ClapilotAICore runtime, with per-pattern verdicts, work packages, and effort estimates. Evaluation only; implementation waits for explicit approval.

This page records the adoption assessment of microsoft/agent-framework release python-1.18.0 (published 2026-09-10 09:23Z, MIT) for ClapilotAICore. It follows the same method as the Commerce-Agents review: read the upstream change at mechanism level, map every idea onto a concrete place in our runtime, and decide per idea whether we take the concept, the code, or nothing.

Process note. This page is the evaluation and adoption reference only. Per the durable decision of 2026-05-03, no implementation starts without explicit approval. Once a work package below is approved, derive the implementation issues from its bullet list; do not implement from this page directly. The origin of this review is Board task d5da5e9c-ea33-419c-9f34-160e6abc3037 (scan 2026-09-10/delta-0956z).

TL;DR

PatternUpstreamVerdictWhy
1. Tool-loop wall-clock bound + stop reason#7772Concept, high value, 3–5 PDOur six provider loops cap steps (24) but never time; heartbeat and job runs get no phase budgets at all, and the only stale-run guard is a 6-hour idle sweep. The step-limit exit is a plain Error with no code, is not persisted, and is invisible to the completion judge. Upstream's mechanism is small and maps directly onto runConversation and executeRun.
2. Shared vector-store abstraction + portable filters#8014, #8115, #8153–#8156Concept, phased, 8–12 PD; no external storeWe run five pgvector tables through bespoke SQL with three copies of the vector-literal helper, two chunkers, two fusion implementations, and no paging. A thin Node collection/filter contract over pgvector is worth it; connectors for Redis, Qdrant, or Azure AI Search are not (none of them exist in our stack, and VISION.md keeps retrieval local-first on Postgres).
AG-UI emit_messages_snapshot opt-out#7808NothingWe do not run an AG-UI server. @ag-ui/core is used only for the CUSTOM event type of our CopilotKit UI cards.
MCP Host-history conversion for AG-UI#8130Nothing, watchSame reason; becomes relevant only if we adopt AG-UI transport for the chat dock.
SecretString as masked wrapper (breaking)#8127Concept, 0.5–1 PD, optionalWe already store encrypted secrets with •••• hints. The upstream point that transfers is type-level masking so a secret cannot be interpolated by accident. Cheap, low urgency.

Total recommended effort: about 12–18 person-days over two work packages plus one optional hygiene item. Nothing here touches the Apple clients except the settings toggle in WP1e.

What the release actually ships

Verified against the GitHub API on 2026-09-10:

  • #7772 (merged 2026-09-08): max_duration_seconds: float | None on FunctionInvocationConfiguration; 3 files, +532/−39, of which 361 lines are tests. The bound is checked after each tool batch (best-effort, not a hard interrupt). When exceeded, the loop sets tool_choice = "none", marks budget_state["truncated"] = True, and forces one final text turn; if the model still returns no visible text, a fixed fallback ("Function invocation limit reached before a final answer could be produced.") is appended. Precedence when several limits trip in the same batch: duration, then consecutive-error stop, then call count, decided in one shared _apply_batch_limit_decision helper so streaming and non-streaming loops cannot disagree. Elapsed time is measured cumulatively across human-approval round-trips because start_time lives in AgentSession.state and is only cleared when no approval is pending.
  • What the scan overstated: #7772 does not ship a machine-readable timeout | max_steps | cancel stop-reason enum. The "stop-reason signal" in the release note is the truncated flag plus an INFO log line naming which check fired; the response carries the ordinary provider finish_reason. The typed stop reason is still the right thing for Clapilot to build, but it is our design, not a port.
  • #8014 (merged 2026-09-04) and #8115 (merged 2026-09-08): a new _vectors.py (~2,000 lines), _vector_filters.py (874 lines) and _in_memory.py (604 lines), all experimental. Contents: @vectorstoremodel record decoration with msgspec codecs, VectorStoreField and VectorStoreCollectionDefinition, batch upsert/get/delete, a single search(search_type="vector" | "keyword_hybrid"), dense-dimension checks at the write and search boundary, a data-only filter tree (Filter(field, operator, value), FilterGroup(AND|OR|NOT), Param for model-set values with inline constraints), create_vector_search_tool that derives a closed JSON schema from the Param declarations, and an in-memory store with pure-Python distance functions. The filter tree replaced an earlier lambda/AST design explicitly for safety.
  • Connectors (all merged 2026-09-09, each ~2,500–3,100 lines): Azure AI Search (#8153, hybrid via reciprocal rank fusion), Redis HASH/JSON (#8156), Qdrant (#8154, alpha), PostgreSQL/pgvector (#8155, alpha). The Postgres connector explicitly does not support hybrid or full-text search, schema migration, or server-side embedding, and does not create extensions or schemas.

1. Tool-loop bound versus ClapilotAICore

Reference: python/packages/core/agent_framework/_tools.py (_apply_batch_limit_decision, _clear_budget_state_from_session, get_response) and _harness/_tool_approval.py. Ours: services/clapilot-agent/src/providers/index.mjs (six loops: runOpenAiResponsesWithTools, runOpenAiLike, runAnthropic, runBedrock, runGemini, runOllama; runConversation), services/clapilot-agent/src/sessions/index.mjs (executeRun), services/clapilot-agent/src/run-latency-budget.mjs, services/clapilot-agent/src/jobs/{heartbeat,index,run-reconciler}.mjs.

MechanismMAF 1.18ClapilotAICoreGap
Iteration capmax_iterations (model round-trips)maxToolSteps = CLAPILOT_MAX_TOOL_STEPS or 24, plus 60 passive-poll credits for website_get_operation, plus one grace turn with tools withdrawn (TOOL_LOOP_STEP_LIMIT_WRAPUP, DB flag tool_budget_wrapup_enabled)None. Ours is equivalent and covers six provider families
Call-count capmax_function_calls, charged only for calls that actually executed (executed_call_count)Batch charged before execution (updateNonPassiveToolStepBudget)Minor; ours is stricter on purpose so passive credits never fund mutations
Repeat guardconsecutive-error stop onlyidentical-batch repeat limit 3 with a warning at 2 (updateToolLoopGuard)Ours is stronger
Wall-clock bound on the loopmax_duration_seconds, checked after each batch, forces a final text turnNone inside any loop. runConversation enforces payload.timeoutMs only when a caller passes timeoutSeconds; resolveRunTimeoutMs documents that "a complete conversation is never capped implicitly". Per-request stall timeouts (PROVIDER_REQUEST_TIMEOUT_MS 120 s) refresh on stream progress, so a slowly streaming turn is unboundedReal gap
Background runssame config applies to every runHeartbeat (jobs/heartbeat.mjs) and automation steps (jobs/index.mjs) call executeRun without timeoutSeconds; phase budgets (providerRequestTimeoutMs, providerFallbackBudgetMs, tool phase) are only attached when interactiveRunReal gap. A heartbeat run is bounded only by 24 steps and provider stalls
Stale-run guardn/arun-reconciler.mjs sweeps agent_runs older than CLAPILOT_AGENT_RUN_MAX_AGE_MINUTES (360) since updated_at, every 10 min, to failed / stuck_timeoutIdle-based, not elapsed-based. A run that keeps calling tools is never reaped
Graceful degradationtool_choice: none, fixed fallback text if the model stays silentgrace turn with activeTools = [] and the wrap-up system message; toolBudgetExhausted: true on the provider resultNone; ours already does this
Machine-readable stop reasonbudget_state.truncated + log line (no enum)Step overshoot throws Error("… exceeded the native tool loop limit (24 steps).") with no code, classified by message heuristics in run-error-classifier.mjs. Grace-turn exhaustion survives only as a localized caveat string and the run.completion_caveats event (kinds: ["tool_budget_exhausted"]), which is suppressed for direct chat. Not on agent_runs, not in run.completed eventDataReal gap, and the one that makes the others hard to observe
Completion judge awarenessn/aautonomous-completion.mjs judge prompt allows BLOCKED "for a hard limit" but never receives the fact that a limit firedGap; the judge can only infer "incomplete"
Budget across approval round-tripscumulative via AgentSession.state["_function_invocation_budget_state"].start_timeFleet approval is a new user message, so a new run and a fresh 24 steps; orchestrator awaiting_approval resumes with a fresh 45-minute Codex TURN_TIMEOUT_MSGap, low priority: our approval flow is a new turn by design
Budget across steer / continuation / restartn/aeach steer continuation (MAX_NATIVE_STEER_CONTINUATIONS 8), the bounded completion continuation, and every restart-recovery re-entry (MAX_EXECUTED_RESTART_RECOVERY_ATTEMPTS 3) starts a fresh maxToolSteps; the age sweep clock resets on requeueGap. Worst case today: 24 × (1 + 8 + 1) steps × 3 restarts with no elapsed cap
CLI bridgesn/aClaude CLI: CLAPILOT_AGENT_CLAUDE_CLI_TIMEOUT_MS unset means no cap and no --max-turns; Codex bridge: hard 45-minute turn timeout with reason: "hard_timeout" checkpointGap on the Claude bridge only
Existing analogueorchestrator-sessions/goal-loop.mjs: maxIterations (20) + maxDurationSeconds (CLAPILOT_GOAL_MAX_DURATION_SECONDS, 14 400) with pause-aware elapsed time and a typed limit: "iterations" | "duration" on the judge resultThis is the pattern to generalize downward

Verdict: take the concept, in our shape. The upstream code is ~130 non-test lines of Python welded to FunctionInvocationConfiguration and the approval middleware; every line would be rewritten. What transfers is the design: one shared limit decision for all loops, a cumulative clock that survives re-entry, graceful degradation instead of a thrown error, and a stop signal the host can read. The goal loop already proves the shape works in our codebase.

WP1 — Run wall-clock budget and typed stop reason (3–5 PD)

  • 1a. Typed stop reason on the provider result (1 PD). Add stopReason to the object every loop returns and to runConversation's result: completed | max_steps | max_duration | repeat_guard | passive_poll_limit | user_abort | steer_interrupt | provider_timeout | conversation_timeout | error. Replace the three plain new Error(...) step-limit throws with the grace-turn path plus stopReason, so a step overshoot is a truncated completed run, not a provider_error. Keep toolBudgetExhausted as a derived boolean for one release. Decide precedence in one helper shared by all six loops, mirroring _apply_batch_limit_decision: duration, then repeat guard, then steps.
  • 1b. Persist and emit it (0.5 PD). New nullable stop_reason column on agent_runs (migration), written by updateRun on every terminal path; add stopReason and elapsedMs to run.completed, run.failed, run.cancelled eventData; include stopReason in the Benchmark benchmark_get_run payload. Update docs/content/api-reference.md and the run-events section of request-execution-flows.md.
  • 1c. maxDurationMs on the loop (1 PD). Read payload.maxDurationMs in the six loops and check it after each tool batch (best-effort, like upstream); when exceeded, enter the existing grace turn with stopReason: "max_duration". Keep runConversation's hard PROVIDER_CONVERSATION_TIMEOUT as the outer backstop at maxDurationMs + finalization allowance, so a provider that ignores the grace turn still terminates. Feed the same signal into the tool combineAbortSignals so a long-running exec_command is cut at the deadline instead of after it.
  • 1d. Defaults per run kind (0.5–1 PD). Direct chat: unchanged (no implicit cap, per the documented decision). Heartbeat: default budget = min(intervalMinutes × 60 s × 0.8, 30 min) so an occurrence can never overlap the next one; automation/job steps: 30 min default; both overridable per schedule in schedule_json.maxDurationSeconds and globally via app_settings (new column next to tool_budget_wrapup_enabled, exposed in Settings → ClapilotAICore → Runtime with an app-styled toggle plus minutes field, DE/EN/IT strings). Heartbeat docs already promise "a truly wedged run cannot pin this heartbeat forever"; this makes it true by elapsed time, not only by idle time.
  • 1e. Cumulative clock across re-entry (0.5–1 PD). Carry budget: { startedAt, consumedMs, consumedSteps } in the run envelope so steer continuations, the bounded completion continuation, and restart-recovery re-entries (input_payload.restartRecovery) draw from the same budget instead of starting over. Mirrors upstream's AgentSession.state persistence. The Claude CLI bridge gets a default timeout derived from the same budget.
  • 1f. Judge and caveat wiring (0.5 PD). Pass stopReason into buildAutonomousJudgePrompt evidence so max_duration / max_steps can legitimately produce blocked with a stated limit instead of incomplete; render the caveat for all run kinds when the stop reason is a limit, including direct chat (today the caveat is suppressed there). Add negative unit tests in providers/index.test.mjs and sessions tests: budget fires mid-loop, budget survives one steer, budget survives one restart recovery, judge sees the reason.

Verification plan for the approved implementation: npm run lint, npx vitest run services/clapilot-agent/src/providers/index.test.mjs services/clapilot-agent/src/run-latency-budget.test.mjs, and a manual heartbeat with a 2-minute budget against a prompt that loops on a tool, checking agent_runs.stop_reason = 'max_duration' and the run.completed event payload.

2. Shared vector-store abstraction versus Clapilot retrieval

Reference: _vectors.py, _vector_filters.py, _in_memory.py, docs/features/vector-stores-and-embeddings/README.md, and the four connector PRs. Ours: src/lib/rag/{retrieve,provider-embeddings}.ts, scripts/document-rag-indexer.mjs, services/clapilot-agent/src/memory/{index,embeddings-v2,retrieval-fusion}.mjs, services/clapilot-agent/src/sessions/index.mjs (context_search fan-out), src/lib/wiki.ts, src/lib/agent-runtime/tool-proxy.ts, and migrations 012, 028, 192, 235.

What we have today

Five pgvector tables, all in the workspace Postgres, no external store and no Redis anywhere in the dependency tree (pg is the only datastore package):

TableVectorANN indexConsumer
document_chunksvector(1536)HNSW cosineMandant-scoped document RAG for chat (retrieve.ts)
agent_memory_chunksvector(1536)HNSW cosinememory_search hybrid arm (vector 0.8 + FTS 0.2)
agent_memory_assertion_embeddingsvector (dims enforced by trigger against agent_memory_embedding_generations)none (sequential scan)memory-v2 approved assertions
agent_knowledge_entity_embeddingsvectornoneknowledge-graph vector arm
agent_knowledge_claim_embeddingsvectornoneknowledge-graph vector arm, RRF-fused with lexical

Shared pieces that already exist: embeddings-v2.mjs (generation descriptor provider:slug:endpointHash:model:dims:preprocessing, L2 normalization, chunk-to-memory collapse), the deterministic cosineHashEmbedding fallback that keeps hash-space and provider-space vectors apart by metadata key, and retrieval-fusion.mjs (RRF with per-source slot reservation) behind context_search.

What is not shared: toVectorLiteral exists three times with two different precisions; there are two chunkers (chunkText in memory, splitIntoChunks in the indexer) and two RRF implementations (fuseRetrievalResults, fuseKnowledgeRankedItems); every surface writes its own parameterized SQL with hand-maintained $n bookkeeping; scoping is one nullable predicate for documents (mandant_id) and a five-rung visibility ladder for memory (appendMemoryVisibilityParams); only wiki_search has offset paging, and it has no tenant or user scoping at all; documents_list never touches document_chunks; there is no Notizen search tool; the learning arm of context_search uses ILIKE and ignores the tsvector index from migration 107; and wiki_search's schema declares search while the fan-out passes query.

Mechanism comparison

MechanismMAF 1.18ClapilotAssessment
Record/collection definitionVectorStoreField(key | data | vector, dimensions, distance, index_kind, provider_annotations), one definition per model, msgspec codecsImplicit in five migrations and five write paths; dims hardcoded 1536 in two places, trigger-enforced in threeTake the concept: one definition object per table in Node, used by both the indexer and the runtime, closes the drift between the 1536 constants and the generation-keyed tables
Batch CRUDupsert/get/delete over sequences, no atomicity promise, generated keys optionalIndexer: delete-then-insert per document inside a transaction; memory: per-row upsertOurs is fine; a shared upsertBatch would remove the duplicated vector-literal code only
Searchone search(values | vector, search_type, top, skip, filter, score_threshold, include_vectors)retrieve.ts: over-fetch ×4, JS min-score, per-doc cap, no skip; memory: over-fetch ×3, weighted hybrid, collapse; knowledge: RRF + decayTake the signature, not the semantics: our hybrid arms are ahead of upstream's Postgres connector, which has no hybrid search at all
Filter modeldata-only Filter / FilterGroup(AND, OR, NOT), namespaced provider operators, validated depth/node limits, connector translatesBespoke SQL predicates; visibility helpers append raw SQL fragmentsTake the concept as an internal contract: a small filter tree translated to SQL by one function, with our visibility ladder and Mandant scope expressed as fixed filters the model can never override
Model-facing search toolcreate_vector_search_tool derives a closed JSON schema from Param declarations (type, default, constraints, omit_if_none), validates model input before resolving the filterTool schemas are hand-written in tool-definitions.mjs; memory_search has query/scope/limit only; no document or Notizen search toolTake the concept for the missing documents_search and notizen_search tools and to fix the wiki_search schema drift
Dimension checkssequence length checked at write and search boundary, error names record index and fieldTriggers on three tables; indexer trusts embeddingDimensions; validateAndNormalizeEmbedding on the memory-v2 path onlySmall, fold into the definition object
In-memory storezero-dependency, same contract, for testsnone; retrieval SQL is tested against a live DB or not at all, and every retrieval query swallows errors into rows: []Take: an in-memory backend behind the same interface makes context_search fusion and scoping testable in vitest
ConnectorsAzure AI Search, Redis, Qdrant, Postgres (alpha)Postgres onlyNothing. No customer or VISION.md requirement asks for a second store; every connector is Python; the pgvector connector is alpha and weaker than what we run
Embedding client abstractionSupportsGetEmbeddings / BaseEmbeddingClient with generic input typesresolveEmbeddingConfig fallback chain in providers/index.mjs, imported across the service boundary by provider-embeddings.tsAlready adequate; leave

Verdict: concept only, phased, Postgres only. The strategic value the scan identified ("unified retrieval API behind Wiki, Notizen, Mandanten RAG; backend swappable") is real for the first half and not needed for the second. A backend seam falls out of the collection contract for free; we should not pay for an implementation of it until a store other than Postgres is actually required.

WP2 — Shared retrieval contract over pgvector (8–12 PD)

  • 2a. Collection definitions and vector codec (1.5 PD). New module services/clapilot-agent/src/retrieval/collections.mjs (imported by src/lib/rag the same way provider-embeddings.ts already imports the provider module): one definition per vector table (key, data fields, vector field, dimensions source, distance, index kind), one toVectorLiteral, one dimension check with record index and field name in the error, one chunkText with the indexer's sentence-boundary behaviour. Delete the three copies. Unit tests for the codec and chunker.
  • 2b. Portable filter tree and SQL translation (2 PD). Filter, FilterGroup with and | or | not, operators eq, ne, in, nin, gt, gte, lt, lte, contains, isNull, depth and node limits, and one translateFilterToSql(filter, definition, paramAccumulator). Express the memory visibility ladder, the memory partition rule, the knowledge audience scopes, and the document Mandant scope as fixed filters produced by the existing helpers, so scoping stays server-owned and the JS isMemoryVisible re-check remains as defence in depth. Property-style tests that every translation stays parameterized.
  • 2c. search() and upsertBatch() over the definitions (2–3 PD). search({ vector | query, searchType: "vector" | "hybrid", top, skip, filter, scoreThreshold }) returning { record, score, source }, with the hybrid arm using the table's content_tsv column where one exists. Migrate retrieve.ts (document RAG), the memory chunk vector and keyword arms, and the two knowledge vector queries to it; leave the memory-v2 assertion query and lexical knowledge query for a later pass. Keep the existing RRF in retrieval-fusion.mjs as the single fusion implementation and retire fuseKnowledgeRankedItems into it. Add paging to memory_search and context_search (skip), documented in agent-tool-contracts.md.
  • 2d. In-memory backend for tests (1 PD). Same contract over a Map with cosine/dot/euclidean, used by vitest for context_search fusion, scoping, and threshold behaviour. Not a production path.
  • 2e. Param-derived search tools (1.5–2 PD). A createSearchTool helper that builds a closed tool schema from declared Params and fixed filters. Use it to add documents_search (vector search over document_chunks with mandant_id, typ, kategorie params; today documents_list is ILIKE only) and notizen_search (FTS over notes, personal-first scoping), and to regenerate wiki_search so its schema and handler agree on query. Wire the new tools through the tool proxy, the MCP allowlists, agent-tool-contracts.md, the coverage matrix, and DE/EN/IT tool descriptions where user-visible.
  • 2f. ANN indexes for the unindexed tables (0.5 PD, optional). HNSW on the three trigger-enforced tables once the definition object owns dimensions per generation; measure before and after with services/clapilot-agent/scripts/memory-bench.mjs.

Verification plan for the approved implementation: npm run lint, npx vitest run services/clapilot-agent/src/retrieval services/clapilot-agent/src/memory, npm run db:migrate on a fresh local DB, scripts/document-rag-indexer.mjs over a Mandant with at least two documents, then a chat question that must cite [Q1], and a context_search call with sources: ["memory","knowledge_graph"] comparing hit sets before and after the migration on the same data.

3. Secondary items

  • AG-UI emit_messages_snapshot (#7808). Clapilot imports @ag-ui/core only for EventType.CUSTOM in src/lib/copilotkit-ui.ts to tag structured UI cards; the chat transport is our own SSE stream from src/app/api/chat/route.ts, and @copilotkit/react-* is installed but not imported. There is no MessagesSnapshot to suppress. Nothing to do.
  • MCP Host-history conversion (#8130). Solves a problem of AG-UI persistence (keeping Host/UI payloads out of the model-facing history). Our equivalent separation already exists: copilot_ui_* tool results are rendered from run events, and the model-facing history is rebuilt from agent_context_messages. Watch only; revisit if the chat dock moves to AG-UI transport.
  • SecretString as a masked value wrapper (#8127). Upstream changed the type from a str subclass to a wrapper with get_secret_value() so a secret can no longer be formatted or logged by accident. Our storage side is already sound (encrypted at rest, hintFromSecret / •••• suffix hints in src/lib/agent-runtime/config.ts, regex redaction in email-provider-errors.ts and knowledge-graph-view.ts). The transferable idea is a tiny branded type or class for decrypted credentials in the provider request path with an explicit reveal() and a toString() that returns the hint, so a stray template literal cannot leak a key into agent_model_request_logs or a run event. 0.5–1 PD, optional, no user visible change.

Recommendation and effort

Sequence WP1 first. It is small, it closes an operational hole that the heartbeat SLO (< 1 % failed scheduled runs) depends on, and its typed stopReason is the same signal the Commerce-Agents review's WP1a (blocked tool outcome) wants on the run level; the two should land on the same agent_runs migration if both are approved.

WP2 second, and only 2a–2c before deciding on 2d–2f. The first three packages pay for themselves in deleted duplication and testability; the tool-schema helper and the new search tools are the user-visible part and should be scoped with the Dokumente and Notizen owners.

Explicitly not recommended:

  • Any MAF Python package as a dependency; the runtime is Node ESM.
  • Redis, Qdrant, or Azure AI Search connectors, or a "backend swappable" implementation beyond the interface seam. Local-first Postgres is the documented product position, and upstream's own pgvector connector is alpha with fewer features than our current queries.
  • A hard interrupt for maxDurationMs inside a tool call. Upstream chose a best-effort check between batches for the same replay-safety reason our shouldEnforceToolPhaseBudget stops enforcing once a tool has completed; the outer PROVIDER_CONVERSATION_TIMEOUT backstop covers the rest.
  • Changing direct chat to an implicit end-to-end deadline; the documented decision stands, and WP1 only adds the reason signal there.

Sources

  • Release: https://github.com/microsoft/agent-framework/releases/tag/python-1.18.0 (2026-09-10T09:23:52Z).
  • Pull requests read at mechanism level: #7772 (_tools.py, _harness/_tool_approval.py, test_function_invocation_logic.py), #8014, #8115 (_vectors.py, _vector_filters.py, _in_memory.py, docs/features/vector-stores-and-embeddings/README.md), #8153, #8154, #8155, #8156 (descriptions and file lists), #7808, #8130, #8127 (descriptions).
  • Clapilot counterparts: services/clapilot-agent/src/providers/index.mjs, services/clapilot-agent/src/sessions/index.mjs, services/clapilot-agent/src/run-latency-budget.mjs, services/clapilot-agent/src/run-completion-caveats.mjs, services/clapilot-agent/src/autonomous-completion.mjs, services/clapilot-agent/src/jobs/{heartbeat,index,run-reconciler}.mjs, services/clapilot-agent/src/orchestrator-sessions/goal-loop.mjs, services/clapilot-agent/src/memory/{index,embeddings-v2,retrieval-fusion}.mjs, services/clapilot-agent/src/tool-definitions.mjs, src/lib/rag/{retrieve,provider-embeddings}.ts, scripts/document-rag-indexer.mjs, src/lib/wiki.ts, src/lib/agent-runtime/tool-proxy.ts, src/lib/copilotkit-ui.ts, db/migrations/{012_rag_pgvector,028_native_agent_runtime,192_memory_v2_assertion_ledger,235_knowledge_graph_vector_retrieval,284_app_settings_completion_safety_hardening}.sql.