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
| Pattern | Upstream | Verdict | Why |
|---|---|---|---|
| 1. Tool-loop wall-clock bound + stop reason | #7772 | Concept, high value, 3–5 PD | Our 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–#8156 | Concept, phased, 8–12 PD; no external store | We 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 | #7808 | Nothing | We 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 | #8130 | Nothing, watch | Same reason; becomes relevant only if we adopt AG-UI transport for the chat dock. |
SecretString as masked wrapper (breaking) | #8127 | Concept, 0.5–1 PD, optional | We 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 | NoneonFunctionInvocationConfiguration; 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 setstool_choice = "none", marksbudget_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_decisionhelper so streaming and non-streaming loops cannot disagree. Elapsed time is measured cumulatively across human-approval round-trips becausestart_timelives inAgentSession.stateand is only cleared when no approval is pending. - What the scan overstated: #7772 does not ship a machine-readable
timeout | max_steps | cancelstop-reason enum. The "stop-reason signal" in the release note is thetruncatedflag plus an INFO log line naming which check fired; the response carries the ordinary providerfinish_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:@vectorstoremodelrecord decoration with msgspec codecs,VectorStoreFieldandVectorStoreCollectionDefinition, batchupsert/get/delete, a singlesearch(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),Paramfor model-set values with inline constraints),create_vector_search_toolthat derives a closed JSON schema from theParamdeclarations, 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.
| Mechanism | MAF 1.18 | ClapilotAICore | Gap |
|---|---|---|---|
| Iteration cap | max_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 cap | max_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 guard | consecutive-error stop only | identical-batch repeat limit 3 with a warning at 2 (updateToolLoopGuard) | Ours is stronger |
| Wall-clock bound on the loop | max_duration_seconds, checked after each batch, forces a final text turn | None 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 unbounded | Real gap |
| Background runs | same config applies to every run | Heartbeat (jobs/heartbeat.mjs) and automation steps (jobs/index.mjs) call executeRun without timeoutSeconds; phase budgets (providerRequestTimeoutMs, providerFallbackBudgetMs, tool phase) are only attached when interactiveRun | Real gap. A heartbeat run is bounded only by 24 steps and provider stalls |
| Stale-run guard | n/a | run-reconciler.mjs sweeps agent_runs older than CLAPILOT_AGENT_RUN_MAX_AGE_MINUTES (360) since updated_at, every 10 min, to failed / stuck_timeout | Idle-based, not elapsed-based. A run that keeps calling tools is never reaped |
| Graceful degradation | tool_choice: none, fixed fallback text if the model stays silent | grace turn with activeTools = [] and the wrap-up system message; toolBudgetExhausted: true on the provider result | None; ours already does this |
| Machine-readable stop reason | budget_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 eventData | Real gap, and the one that makes the others hard to observe |
| Completion judge awareness | n/a | autonomous-completion.mjs judge prompt allows BLOCKED "for a hard limit" but never receives the fact that a limit fired | Gap; the judge can only infer "incomplete" |
| Budget across approval round-trips | cumulative via AgentSession.state["_function_invocation_budget_state"].start_time | Fleet 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_MS | Gap, low priority: our approval flow is a new turn by design |
| Budget across steer / continuation / restart | n/a | each 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 requeue | Gap. Worst case today: 24 × (1 + 8 + 1) steps × 3 restarts with no elapsed cap |
| CLI bridges | n/a | Claude 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" checkpoint | Gap on the Claude bridge only |
| Existing analogue | — | orchestrator-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 result | This 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
stopReasonto the object every loop returns and torunConversation's result:completed | max_steps | max_duration | repeat_guard | passive_poll_limit | user_abort | steer_interrupt | provider_timeout | conversation_timeout | error. Replace the three plainnew Error(...)step-limit throws with the grace-turn path plusstopReason, so a step overshoot is a truncatedcompletedrun, not aprovider_error. KeeptoolBudgetExhaustedas 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_reasoncolumn onagent_runs(migration), written byupdateRunon every terminal path; addstopReasonandelapsedMstorun.completed,run.failed,run.cancelledeventData; includestopReasonin the Benchmarkbenchmark_get_runpayload. Updatedocs/content/api-reference.mdand the run-events section ofrequest-execution-flows.md. - 1c.
maxDurationMson the loop (1 PD). Readpayload.maxDurationMsin the six loops and check it after each tool batch (best-effort, like upstream); when exceeded, enter the existing grace turn withstopReason: "max_duration". KeeprunConversation's hardPROVIDER_CONVERSATION_TIMEOUTas the outer backstop atmaxDurationMs + finalization allowance, so a provider that ignores the grace turn still terminates. Feed the same signal into the toolcombineAbortSignalsso a long-runningexec_commandis 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 inschedule_json.maxDurationSecondsand globally viaapp_settings(new column next totool_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'sAgentSession.statepersistence. The Claude CLI bridge gets a default timeout derived from the same budget. - 1f. Judge and caveat wiring (0.5 PD). Pass
stopReasonintobuildAutonomousJudgePromptevidence somax_duration/max_stepscan legitimately produceblockedwith a stated limit instead ofincomplete; 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 inproviders/index.test.mjsandsessionstests: 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):
| Table | Vector | ANN index | Consumer |
|---|---|---|---|
document_chunks | vector(1536) | HNSW cosine | Mandant-scoped document RAG for chat (retrieve.ts) |
agent_memory_chunks | vector(1536) | HNSW cosine | memory_search hybrid arm (vector 0.8 + FTS 0.2) |
agent_memory_assertion_embeddings | vector (dims enforced by trigger against agent_memory_embedding_generations) | none (sequential scan) | memory-v2 approved assertions |
agent_knowledge_entity_embeddings | vector | none | knowledge-graph vector arm |
agent_knowledge_claim_embeddings | vector | none | knowledge-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
| Mechanism | MAF 1.18 | Clapilot | Assessment |
|---|---|---|---|
| Record/collection definition | VectorStoreField(key | data | vector, dimensions, distance, index_kind, provider_annotations), one definition per model, msgspec codecs | Implicit in five migrations and five write paths; dims hardcoded 1536 in two places, trigger-enforced in three | Take 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 CRUD | upsert/get/delete over sequences, no atomicity promise, generated keys optional | Indexer: delete-then-insert per document inside a transaction; memory: per-row upsert | Ours is fine; a shared upsertBatch would remove the duplicated vector-literal code only |
| Search | one 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 + decay | Take the signature, not the semantics: our hybrid arms are ahead of upstream's Postgres connector, which has no hybrid search at all |
| Filter model | data-only Filter / FilterGroup(AND, OR, NOT), namespaced provider operators, validated depth/node limits, connector translates | Bespoke SQL predicates; visibility helpers append raw SQL fragments | Take 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 tool | create_vector_search_tool derives a closed JSON schema from Param declarations (type, default, constraints, omit_if_none), validates model input before resolving the filter | Tool schemas are hand-written in tool-definitions.mjs; memory_search has query/scope/limit only; no document or Notizen search tool | Take the concept for the missing documents_search and notizen_search tools and to fix the wiki_search schema drift |
| Dimension checks | sequence length checked at write and search boundary, error names record index and field | Triggers on three tables; indexer trusts embeddingDimensions; validateAndNormalizeEmbedding on the memory-v2 path only | Small, fold into the definition object |
| In-memory store | zero-dependency, same contract, for tests | none; 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 |
| Connectors | Azure AI Search, Redis, Qdrant, Postgres (alpha) | Postgres only | Nothing. 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 abstraction | SupportsGetEmbeddings / BaseEmbeddingClient with generic input types | resolveEmbeddingConfig fallback chain in providers/index.mjs, imported across the service boundary by provider-embeddings.ts | Already 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 bysrc/lib/ragthe same wayprovider-embeddings.tsalready imports the provider module): one definition per vector table (key, data fields, vector field, dimensions source, distance, index kind), onetoVectorLiteral, one dimension check with record index and field name in the error, onechunkTextwith 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,FilterGroupwithand | or | not, operatorseq, ne, in, nin, gt, gte, lt, lte, contains, isNull, depth and node limits, and onetranslateFilterToSql(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 JSisMemoryVisiblere-check remains as defence in depth. Property-style tests that every translation stays parameterized. - 2c.
search()andupsertBatch()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'scontent_tsvcolumn where one exists. Migrateretrieve.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 inretrieval-fusion.mjsas the single fusion implementation and retirefuseKnowledgeRankedItemsinto it. Add paging tomemory_searchandcontext_search(skip), documented inagent-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_searchfusion, scoping, and threshold behaviour. Not a production path. - 2e. Param-derived search tools (1.5–2 PD). A
createSearchToolhelper that builds a closed tool schema from declaredParams and fixed filters. Use it to adddocuments_search(vector search overdocument_chunkswithmandant_id,typ,kategorieparams; todaydocuments_listisILIKEonly) andnotizen_search(FTS over notes, personal-first scoping), and to regeneratewiki_searchso its schema and handler agree onquery. 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/coreonly forEventType.CUSTOMinsrc/lib/copilotkit-ui.tsto tag structured UI cards; the chat transport is our own SSE stream fromsrc/app/api/chat/route.ts, and@copilotkit/react-*is installed but not imported. There is noMessagesSnapshotto 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 fromagent_context_messages. Watch only; revisit if the chat dock moves to AG-UI transport. SecretStringas a masked value wrapper (#8127). Upstream changed the type from astrsubclass to a wrapper withget_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 insrc/lib/agent-runtime/config.ts, regex redaction inemail-provider-errors.tsandknowledge-graph-view.ts). The transferable idea is a tiny branded type or class for decrypted credentials in the provider request path with an explicitreveal()and atoString()that returns the hint, so a stray template literal cannot leak a key intoagent_model_request_logsor 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
maxDurationMsinside a tool call. Upstream chose a best-effort check between batches for the same replay-safety reason ourshouldEnforceToolPhaseBudgetstops enforcing once a tool has completed; the outerPROVIDER_CONVERSATION_TIMEOUTbackstop 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.
