Aevatar context database#4
Closed
eanzhao wants to merge 4 commits into
Closed
Conversation
…ation - Introduce a comprehensive OCP architecture refactor plan to address current implementation issues, focusing on modularity, extensibility, and adherence to the Open/Closed Principle. - Document a phased approach for refactoring, including the introduction of new modules, plugin architecture, and clear separation of concerns. - Add a PR review audit document outlining strict architectural and coding standards, identifying key issues, and providing a risk assessment for the current implementation. This commit enhances project documentation and sets a clear path for future architectural improvements.
This was referenced Apr 6, 2026
louis4li
added a commit
that referenced
this pull request
Apr 9, 2026
This was referenced Apr 13, 2026
This was referenced Apr 21, 2026
eanzhao
added a commit
that referenced
this pull request
Apr 27, 2026
…ServiceId; share contract math Addresses PR #457 review. ## Functional fix (the inline review): InvokePath / invoke handler mismatch The contract returned by the new `GET /members/.../endpoints/.../contract` was telling the frontend to call `/members/{memberId}/invoke/...`, but the existing platform handler for that path resolves the member through `IMemberPublishedServiceResolver` which today returns `publishedServiceId == memberId`. Studio's bind path persists `publishedServiceId == "member-{memberId}"`. So the contract was built for `member-{memberId}` while invoke would target `{memberId}` → 404. Fix: register `StudioAwareMemberPublishedServiceResolver` from Studio's DI. It first asks `IStudioMemberQueryPort` for the member's stored `publishedServiceId`; if no Studio member exists, falls back to the legacy deterministic mapping (`memberId == publishedServiceId`) so direct platform binds keep working unchanged. Now contract / activate / retire / invoke / runs all resolve to the same identity. ## Refactors per the PR review - **#1 Duplicated contract-building logic**: extracted the pure helpers (`ResolveCurrentContractRevision`, `EnumeratePreferredContractRevisionIds`, `RevisionContainsEndpoint`, `IsChatEndpoint`, `ResolveStreamFrameFormat`, `BuildBase64PayloadPlaceholder`, `BuildTypedInvokeRequestExampleBody`) into `Aevatar.GAgentService.Abstractions.Services.ServiceEndpointContractMath`. Both `ScopeServiceEndpoints.cs` (legacy) and `StudioMemberService.cs` (member-first) funnel through it. A bug fix in one helper now propagates to both paths automatically. - **#3 / #4 Repeated resolve+verify pattern**: introduced `ResolveBoundServiceContextAsync` returning `(ScopeId, MemberId, PublishedServiceId, Identity, Service, Revisions)`. The three new methods now all share one query path; activate / retire dropped from 4 platform queries to 2. - **#2 Non-atomic activate**: documented with a `NOTE:` comment that `SetDefaultServingRevision` then `ActivateServiceRevision` is intentionally non-transactional, mirroring the legacy scope-default behavior, and that both commands are platform-side idempotent. - **#7 Hardcoded "retired" string**: introduced `MemberRevisionLifecycleStatusNames.Retired` next to the existing `MemberLifecycleStageNames` so future lifecycle verbs declare themselves alongside it instead of as scattered magic strings. - **#6 / #8 Input trimming**: collapsed the four ad-hoc trimming sites into a single `NormalizeRequired(value, fieldName)` helper applied at the service entry of every public method. Trimming now happens at exactly one boundary per call. ## Tests - 13 new tests pin the resolver's contract (Studio member → stored publishedServiceId; non-Studio member → legacy fallback; trim; reject malformed input; empty publishedServiceId degrades safely). - Existing tests unchanged: 327 Studio + 281 platform integration passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eanzhao
added a commit
that referenced
this pull request
Apr 27, 2026
P1 — workflow termination on publish failure (reviewer's critical):
`PublishFailureAsync` emits `StepCompletedEvent { Success = false }`,
which the kernel routes through `TryRetryAsync` → `TryOnErrorAsync` →
fail. With no retry/on_error policy on `publish_to_twitter`, a Twitter
401/403/429/5xx terminated the entire workflow run as failed. Add
`on_error: { strategy: skip, default_output: "twitter_publish_failed" }`
to the YAML so the run advances to `done` cleanly; the module already
surfaces categorized errors to Lark independently.
#2 — Twitter v2 native error shape: `ClassifyTwitterResponse` now
recognizes the third response shape NyxID can forward verbatim:
`{ "title": "...", "detail": "...", "errors": [...] }` (Twitter's
native problem-details for content-policy / duplicate-tweet rejections).
Falls through to `twitter_publish_rejected` with the Twitter `message`
text in the Lark surfacing so users read the actual rejection reason.
#1 — Duplicate tweet risk: documented in code comment that
`POST /2/tweets` has no server-side dedup; the social_media template
intentionally has no `retry` policy on this step, and `on_error: skip`
advances rather than retrying. Authors customizing the YAML must keep
this invariant.
#3 — Removed redundant `nyxClient!` null-forgiving (no-op cleanup).
#4 — Renamed `ChannelMetadataKeys.LarkProxySlug` →
`LarkOutboundProxySlug` (`channel.lark.outbound_proxy_slug`) to
disambiguate "Lark API surface" from "NyxID provider routing".
#5 — Added xml-doc on `TrySendLarkAsync` documenting the dual-scope
api-key dependency (key must carry both api-twitter AND api-lark-bot
entitlements) so future callers don't silently break Lark surfacing
when narrowing the key's scope.
#6 — Added `RequiredServiceSlugs` field to `SocialMediaTemplateSpec`
for parity with `DailyReportTemplateSpec`; `CreateSocialMediaAgentAsync`
now reads slugs from the spec instead of inlining the list.
Tests:
- 3 new `ClassifyTwitterResponse` tests for the Twitter native error
shapes (errors-array, RFC-7807 title/detail-only, empty-object
unexpected-shape).
- Existing social_media test now also asserts `strategy: skip` lands in
the upserted YAML.
- 482 channel-runtime + 236 workflow.core tests pass; full solution
builds with 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eanzhao
added a commit
that referenced
this pull request
Apr 27, 2026
…elper Resolves three followup architecture-review points on PR #451: ### Channel.Runtime drops AI / Workflow direct deps (review #2) `Channel.Runtime`'s csproj used to pull in `Aevatar.AI.Abstractions` and `Aevatar.Workflow.Application.Abstractions` because of two files that straddled the channel/AI and channel/workflow boundary: - `ChannelContextMiddleware` (an `ILLMCallMiddleware` impl) — moved to `Aevatar.GAgents.NyxidChat`, which is the only package that needs it and already references `AI.Abstractions`. NyxidChat SCE registers it for the LLM call pipeline; Channel.Runtime SCE no longer touches `ILLMCallMiddleware`. - `ChannelCardActionRouting` (builds `WorkflowResumeCommand`) — moved to `Aevatar.GAgents.NyxidChat` for the same reason. Its sole consumer (`ChannelConversationTurnRunner`) lives there too. `Channel.Runtime.csproj` now references only `Channel.Abstractions`, `Foundation.Abstractions`/`Core`, and the `CQRS.Projection.*` slice — matching the "channel-agnostic flow + projection infrastructure" charter from the RFC. Tests (`ChannelCardActionRoutingTests`) get the extra `using Aevatar.GAgents.NyxidChat;`. ### Extract Elasticsearch projection-store toggle helper (review #4) The `ResolveElasticsearchEnabled` + `BuildElasticsearchOptions` helper pair was duplicated three times (Channel.Runtime / Device / Scheduled SCEs) with slightly different log strings and `Console.Error.WriteLine` output. Centralized into `Aevatar.CQRS.Projection.Providers.Elasticsearch.DependencyInjection.ElasticsearchProjectionConfiguration` with two static helpers: - `IsEnabled(IConfiguration?, ILogger?, string? storeName)` — explicit flag → endpoints presence → false; logs a structured warning via `ILogger` (when supplied) instead of `Console.Error.WriteLine`. - `BindOptions(IConfiguration)` — typed binder for `ElasticsearchProjectionDocumentStoreOptions`. All three SCEs now call into this helper; per-package warning text is parameterized via `storeName`. Section path (`Projection:Document:Providers:Elasticsearch`) is exposed as a const so future call sites stay in sync. ### Followup points acknowledged but deferred - **Cross-package dep chain `NyxidChat → Authoring.Lark → Scheduled → Platform.Lark`** (review #1) — pre-existing arch debt that the split surfaced rather than introduced. Cleaner would be to invert via `IInboundFlowResolver` plug-ins so `ChannelConversationTurnRunner` doesn't reach into `AgentBuilderCardFlow` directly. Out of scope for the package split; tracking as a separate follow-up. - **Tombstone compactor "central coordinator"** (review #3) — `Channel.Runtime` defines `ITombstoneCompactionTarget` but does not reference `Device` / `Scheduled` at the csproj level; per-package targets register themselves through DI. The plug-in pattern is intentional and keeps the DAG one-way. - **`Scheduled` package name vs UserAgentCatalog content** (review #5) — `UserAgentCatalog` is the delivery-target registry that Scheduled agents read at execution time to route output, so co-locating it with `SkillRunnerGAgent` / `WorkflowAgentGAgent` is intentional. Renaming to `AgentCatalog` would split actors from their primary consumer; deferring. 473/473 ChannelRuntime.Tests pass; full slnx still only fails the same two pre-existing Mainnet hosting `BindAsync on IStudioMemberService` tests that reproduce on origin/dev. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 27, 2026
Merged
eanzhao
added a commit
that referenced
this pull request
Apr 29, 2026
…ash-command Wires up all six pieces the ADR called out as parallel-capable now that NyxID#549 has shipped, so per-user binding stops requiring a follow-up PR per layer: #1 — AuthContext.external_subject (Channel.Abstractions) Move ExternalSubjectRef out of Identity.Abstractions into Channel.Abstractions where AuthContext lives. Identity is a consumer of channel concepts, not their owner; the new typed AuthContext.external_subject = 4 field is the broker-mode outbound identity carrier (ADR §Outbound Send) and the legacy string user_credential_ref keeps working for non-broker callers. Adds a new AuthContext.OnBehalfOfExternalSubject helper. #2 — Projection chain ExternalIdentityBindingDocument (proto) + Partial (IProjectionReadModel) + MetadataProvider + MaterializationContext + Projector (ICurrentStateProjectionMaterializer) + ExternalIdentityBindingProjectionQueryPort + ExternalIdentityBindingProjectionReadinessPort (polling impl, write-side completion path only — ADR §Projection Readiness explicitly allows this). AddChannelIdentityProjection registers the lot. #3 — NyxIdRemoteCapabilityBroker HTTP-backed INyxIdCapabilityBroker + INyxIdBrokerCallbackClient against the NyxID#549 wire shape: /oauth/authorize URL building, RFC 8693 token-exchange with subject_token_type=urn:nyxid:params:oauth:token-type:binding-id, /oauth/bindings/{id} delete, authorization-code -> binding_id exchange. Maps invalid_grant -> BindingRevokedException so the upper layer event-source-revokes the local binding. PkceHelper covers RFC 7636 S256; StateTokenCodec seals correlation+verifier+exp into an HMAC token (kid header, deterministic Protobuf payload — ADR §Implementation Notes #1). AddNyxIdRemoteCapabilityBroker wires HttpClient + options + codec + both interfaces. #4 — /api/oauth/nyxid-callback endpoint (IdentityOAuthEndpoints) Decodes state, exchanges code -> binding_id, dispatches CommitBindingCommand to ExternalIdentityBindingGAgent via IActorRuntime, waits on the projection readiness port, returns a friendly bind-confirmation page (id_token decoded locally for the display name — no /oauth/userinfo round-trip per ADR L61). Classifies error UX by HTTP status (ADR §Implementation Notes #3): 400 for state-token issues, 502 for broker exchange failure, 200 with a "binding pending propagation" message on projection timeout. #5 — Slash-command routing in ChannelConversationTurnRunner /init and /unbind are handled before the LLM by a new TryHandleSlashCommandAsync that resolves identity ports lazily through the existing IServiceProvider — deployments without per-user binding fall through unchanged. /init replies with the authorize URL (private DM only — ADR §Decision); /unbind resolves binding_id, calls broker.RevokeBindingAsync, replies with the unbind confirmation. The runner short-circuits via the existing SendReplyAsync, so reply delivery rides the relay outbound port like any other reply. #6 — /api/webhooks/nyxid-broker-revocation receiver (Continuous Access Evaluation) BrokerRevocationWebhookValidator verifies HMAC-SHA256 over the raw body (X-NyxID-Signature: sha256=<hex>), parses the JSON envelope into a typed BrokerRevocationNotification, and the endpoint event-source-revokes the local binding actor. Aligns with NyxID#549 V2-7's CAE channel — when a user revokes from NyxID's console the binding goes inactive in seconds rather than waiting for the 5-min access TTL. Tests: - 591 ChannelRuntime.Tests pass (39 of which are the new Identity-tagged tests covering actor, projector, broker fake, state-token codec, and ExternalSubjectRef extension). - Full solution `dotnet build aevatar.slnx` is clean. - `tools/docs/lint.sh` 33 file(s) checked, 0 errors. The ChannelConversationTurnRunner integration is feature-flag-shaped: if neither IExternalIdentityBindingQueryPort nor INyxIdCapabilityBroker is registered, the slash-command path is a no-op and existing bot-owner-shared behaviour is preserved. Production rollout is a DI toggle (AddChannelIdentityProjection + AddNyxIdRemoteCapabilityBroker + MapIdentityOAuthEndpoints) plus the Bot-Owner-Shared termination strategy choice from the ADR §Bot-Owner-Shared 模式终止策略. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
eanzhao
added a commit
that referenced
this pull request
Apr 29, 2026
Five round-2 follow-ups from the Codex consensus: #5 Orphan binding leak on actor-activation failure (resolved): The OAuth callback's already-bound branch revoked the orphan binding_id; the actor-activation-failed branch (when the local actor cannot be created post-exchange) silently returned 503 and left the freshly issued binding_id active at NyxID. Now the same TryRevokeOrphanBindingAsync helper runs before the 503 response so both leak paths cleanup symmetrically. #3 NyxIdAuthorityResolver footgun on staging clusters (resolved): Resolve() now takes an optional ILogger and emits a warning when AEVATAR_NYXID_AUTHORITY is unset AND ASPNETCORE_ENVIRONMENT / DOTNET_ENVIRONMENT indicate a non-Development environment. Local dev remains silent (env is empty / starts with "dev" / "local"); staging / qa / prod operators see the warning so a forgotten env-var doesn't silently register clients against production NyxID. #1 csproj coupling — store wiring belongs to the host (resolved): Split AddChannelIdentity into two extension methods. AddChannelIdentity now registers actors / projector / broker / slash commands but NOT document stores. AddChannelIdentityProjectionStores wires the ES vs InMemory choice and is called by the composition root (Mainnet host now invokes both methods). Tests / demos can mix and match — agent module never decides on the host's behalf which physical store to use. #2 kid rotation grace window (resolved): HMAC key rotation previously invalidated all in-flight state tokens (kid hardcoded "v1", no multi-key verification). The actor now carries current_kid + previous_kid + previous_demoted_at_unix on its state + projection. Encode signs with the snapshot's current kid; decode tries current first, then previous when demoted_at + state_token_lifetime is still in the future. A v1→v2 rotation produces deterministic kid succession (parses + increments the integer suffix) so verifiers can route signed tokens to the right key. #4 HMAC key still in projection (partial — code unchanged, ADR hardened): The projection-store-as-actor-state-mirror pattern is the established shape in this codebase (Channel.Runtime same coupling). Removing the hmac_key from the document would require a generic actor query/reply pattern that CLAUDE.md explicitly forbids ("禁止 generic actor query/reply"). The ADR-0018 §Implementation Notes #1 already documents the explicit tradeoff (state_token TTL ≤ 5 min, rotation command available, ES index access boundary equals actor event-store boundary, KMS migration path noted as a follow-up). The proto comment on AevatarOAuthClientDocument now also calls out the production access-scoping requirement so a least-privileged ES reader can't extract the key. Encryption-at-rest with an envelope key from env-var is the natural follow-up if a deployment widens ES access beyond the actor event-store boundary. Tests: - StateTokenCodec gains a kid-rotation grace test (decode succeeds with previous kid before lifetime expires; fails after). - AevatarOAuthClientGAgent gains a rotation-demotion test (verifies v1→v2 kid succession + previous-key carry-over) and a first-seed test (no previous-key fields populated on initial provision). - 800 ChannelRuntime tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eanzhao
added a commit
that referenced
this pull request
Apr 30, 2026
19 inline comments arrived after de82e0a; verified each. Three of them (#13, #14, #16) point at the HttpClient captive bug already fixed in de82e0a — those will be answered with a reply. Three are NyxID-side contract gaps (#15, #18, #19) verified against ~/Code/NyxID HEAD cdfef0a; those need separate NyxID PRs and will be tracked. The rest are fixed here: - /model list (codex MAJOR #11): read owner default from context.RegistrationScopeId, not the ambient queryPort overload — channel inbound has no Studio HTTP request behind it, so the ambient resolver returned `default`/unrelated state. Falls back to ambient only when the scope is empty (defensive). Tests pinned. - StateTokenCodec.TryDecodeAsync (consensus MINOR #10): map AevatarOAuthClientNotProvisionedException to a distinct state_client_not_provisioned code instead of state_signature_invalid. IdentityOAuthEndpoints surfaces a "正在初始化, 30 秒后重试" detail for that code, matching the /init handler's cold-start message. - AevatarOAuthClientBootstrapService (#8, #9): - wrap RunWithRetryAsync in RunSafelyAsync that logs any escape so the unobserved-task exception sink is no longer the only safety net. - StopAsync now catches TimeoutException too: when the host shutdown deadline fires before the bootstrap task observes its own _stoppingCts cancellation, log + return cleanly instead of leaking a noisy trace. - AevatarOAuthClientGAgent.HandleEnsureProvisioned (#6, #7): document why CancellationToken.None is the contract — the framework's EventHandlerDiscoverer requires single-parameter handlers, so a turn-scoped CT cannot be surfaced. The named HTTP client's per- request timeout bounds the worst case during silo shutdown. - NyxIdRedirectUriResolver (#4): emit a warning when all URL sources are unset and the environment is not developer-shaped, parity with NyxIdAuthorityResolver's existing fallback warning. Wired through bootstrap + broker call sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 30, 2026
This was referenced May 19, 2026
refactor: cluster-034 projection watermark query → materialized ProjectionScopeStatus readmodel
#726
Merged
This was referenced May 20, 2026
iter26 cluster-030: Telegram /getUpdates actor-owned ExternalLink stream + delete watchdog race
#815
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context Database 架构文档
概述
Aevatar Context Database 将技能、资源、记忆、会话统一抽象为
aevatar://虚拟文件系统,提供:IContextStore)IContextRetriever)本文档以当前实现为准,描述真实行为、默认参数、接入方式和现阶段限制。
灵感来源于 OpenViking,在 Aevatar 分层架构中落地。
模块全景
graph TB subgraph abstractions["Aevatar.Context.Abstractions"] aevatarUri["AevatarUri"] contextStore["IContextStore"] contextRetriever["IContextRetriever"] end subgraph core["Aevatar.Context.Core"] uriMapper["AevatarUriPhysicalMapper"] localFileStore["LocalFileContextStore"] inMemoryStore["InMemoryContextStore"] end subgraph extraction["Aevatar.Context.Extraction"] layerGenerator["LLMContextLayerGenerator"] semanticProcessor["SemanticProcessor"] end subgraph retrieval["Aevatar.Context.Retrieval"] vectorIndex["LocalVectorIndex"] intentAnalyzer["IntentAnalyzer"] hierarchicalRetriever["HierarchicalRetriever"] injectionMiddleware["ContextInjectionMiddleware"] end subgraph memory["Aevatar.Context.Memory"] memoryExtractor["LLMMemoryExtractor"] memoryDeduplicator["MemoryDeduplicator"] memoryWriter["MemoryWriter"] memoryProjector["MemoryExtractionProjector"] end core --> abstractions extraction --> abstractions retrieval --> abstractions memory --> abstractions memory --> retrieval memoryProjector --> cqrsProjection["Aevatar.CQRS.Projection.Abstractions"]虚拟文件系统
URI 格式
Scope 映射
skills~/.aevatar/skills/AevatarPaths.Skillsresources~/.aevatar/resources/AevatarPaths.Resourcesuser~/.aevatar/users/AevatarPaths.Usersagent~/.aevatar/agents/AevatarPaths.AgentDatasession~/.aevatar/sessions/AevatarPaths.Sessions说明:
AevatarPaths.AgentData与AevatarPaths.Agents当前都映射到~/.aevatar/agents/。FromPhysicalPath仅识别上述五个 scope;其他物理路径返回null。ArgumentException。AevatarUri行为要点Scope会归一化为小写。aevatar://scope与aevatar://scope/都视为目录。Path尾部斜杠会被裁剪,目录语义由IsDirectory保留。Parent在 scope 根目录时返回自身。Join("")返回自身,Join("x/")结果为目录,Join("x")结果为文件。存储层行为
LocalFileContextStoreReadAsyncFileNotFoundExceptionWriteAsyncDeleteAsyncrecursiveListAsync.开头隐藏项GlobAsyncExistsAsyncDirectory.Exists;文件用File.ExistsGetAbstractAsync.abstract.md,传文件 URI 时自动取父目录GetOverviewAsync.overview.md,传文件 URI 时自动取父目录额外约束:
GlobAsync主要覆盖简单模式(例如**/*.md),复杂组合模式并非完整 glob 实现。../写入 URI path。InMemoryContextStoreConcurrentDictionary存储文件与目录,适合单测和本地验证。ExistsAsync在存在子文件或子目录时也会返回true。GlobAsync为轻量匹配逻辑,覆盖常见模式但不是完整 glob 语义。分层信息模型(L0/L1/L2)
目标语义
.abstract.md.overview.md当前实现行为
GetAbstractAsync/GetOverviewAsync实际读取其父目录摘要文件。SemanticProcessor采用自底向上处理目录树,目录级 L0/L1 由子摘要聚合生成。graph BT leafFile["LeafFile"] --> generateFileAbstract["GenerateAbstractAsync"] generateFileAbstract --> writeParentAbstract["Write parent .abstract.md"] writeParentAbstract --> aggregateChildAbstracts["Aggregate child abstracts"] aggregateChildAbstracts --> generateDirectoryLayers["GenerateDirectoryLayersAsync"] generateDirectoryLayers --> writeDirectoryLayers["Write .abstract.md and .overview.md"]检索链路
FindAsync(简单搜索)特点:
targetScope限定检索范围。SearchAsync(复杂搜索)sequenceDiagram participant userQuery as UserQuery participant intentAnalyzer as IntentAnalyzer participant hierarchicalRetriever as HierarchicalRetriever participant vectorIndex as IContextVectorIndex userQuery->>intentAnalyzer: AnalyzeAsync(query, session) intentAnalyzer-->>hierarchicalRetriever: TypedQueryArray loop eachTypedQuery hierarchicalRetriever->>vectorIndex: SearchAsync global topK 3 vectorIndex-->>hierarchicalRetriever: GlobalResults loop queueDrillDown hierarchicalRetriever->>vectorIndex: SearchChildrenAsync topK 5 vectorIndex-->>hierarchicalRetriever: ChildResults end end hierarchicalRetriever-->>userQuery: FindResult实现要点:
IntentAnalyzer将输入拆成 0 到 5 条TypedQuery。TypedQuery先做全局检索,再做目录子项下钻。final = 0.5 * child + 0.5 * parent。topScore变化小于0.001。ContextType.Memory的根范围是null,表示跨 user/agent 记忆检索。检索默认参数
IntentAnalyzerMaxTokens500IntentAnalyzerTemperature0.0IntentAnalyzerRecentMessagesLimit5(TakeLast(5))IntentAnalyzerMaxTypedQueries5HierarchicalRetrieverGlobalSearchTopK3HierarchicalRetrieverSearchChildrenTopK5HierarchicalRetrieverFinalTake10HierarchicalRetrieverScorePropagationAlpha0.5HierarchicalRetrieverMaxConvergenceRounds3HierarchicalRetrieverConvergenceThreshold0.001ContextInjectionMiddlewareMaxContextTokenBudget3000ContextInjectionMiddleware3000 * 4字符估算ContextInjectionMiddlewareFindAsync上下文注入中间件
ContextInjectionMiddleware行为:user消息作为查询。system消息插入对话。记忆链路
6 类记忆
Profileaevatar://user/{userId}/memories/Preferencesaevatar://user/{userId}/memories/preferences/Entitiesaevatar://user/{userId}/memories/entities/Eventsaevatar://user/{userId}/memories/events/Casesaevatar://agent/{agentId}/memories/cases/Patternsaevatar://agent/{agentId}/memories/patterns/提取与去重流程
去重决策矩阵:
< 0.85Create>= 0.85且分类可合并Update> 0.95且分类不可合并Skip>= 0.85且分类不可合并且不满足 skip 条件Create可合并类别:
Profile、Preferences、Entities、Patterns。不可合并类别:
Events、Cases。记忆模块默认参数
LLMMemoryExtractorMaxTokens2000LLMMemoryExtractorTemperature0.0LLMMemoryExtractor10000字符MemoryDeduplicatorSimilarityThreshold0.85MemoryDeduplicatorSkipThreshold0.95MemoryDeduplicatortopK3MemoryWriteryyyyMMdd-HHmmss-{slug}.mdMemoryWriter\\n\\n---\\n\\nMemoryExtractionProjectorOrder200MemoryExtractionProjectoruserId/agentId"default"Projection Pipeline 集成
MemoryExtractionProjector<TContext, TTopology>为泛型投影器,实现IProjectionProjector<TContext, TTopology>。graph LR eventEnvelope["EventEnvelopeStream"] --> projectionCoordinator["ProjectionCoordinator"] projectionCoordinator --> readModelProjector["WorkflowExecutionReadModelProjector(Order=0)"] projectionCoordinator --> aguiEventProjector["WorkflowExecutionAGUIEventProjector(Order=100)"] projectionCoordinator --> memoryExtractionProjector["MemoryExtractionProjector(Order=200)"] memoryExtractionProjector --> extractionFlow["CompleteAsync -> Extract -> Deduplicate -> Write"]项目依赖图
graph TD contextAbstractions["Aevatar.Context.Abstractions"] contextCore["Aevatar.Context.Core"] contextExtraction["Aevatar.Context.Extraction"] contextRetrieval["Aevatar.Context.Retrieval"] contextMemory["Aevatar.Context.Memory"] aevatarConfig["Aevatar.Configuration"] aiAbstractions["Aevatar.AI.Abstractions"] projectionAbstractions["Aevatar.CQRS.Projection.Abstractions"] contextCore --> contextAbstractions contextCore --> aevatarConfig contextExtraction --> contextAbstractions contextExtraction --> aiAbstractions contextRetrieval --> contextAbstractions contextRetrieval --> aiAbstractions contextMemory --> contextAbstractions contextMemory --> contextRetrieval contextMemory --> aiAbstractions contextMemory --> projectionAbstractionsDI 注册与启用
模块注册
AddContextStore()会调用AevatarPaths.EnsureContextDirectories(),确保基础目录存在。Bootstrap 集成
启用后会追加注册:
AddContextStore()AddContextExtraction()AddContextRetrieval()AddContextMemory()ILLMCallMiddleware -> ContextInjectionMiddlewareWorkflow Projection 集成
扩展点
IContextStoreLocalFileContextStoreIContextVectorIndexLocalVectorIndexIContextLayerGeneratorLLMContextLayerGeneratorIMemoryExtractorLLMMemoryExtractorIEmbeddingGenerator当前实现限制与优化方向
../穿越MemoryExtractionProjector当前使用硬编码default用户与 AgentuserId与agentIdContextInjectionMiddleware使用字符数近似 tokenLocalFileContextStore仅覆盖简化 glob 模式DeduplicationDecision.Merge已定义,当前去重器不产出该分支MemoryWriter.Merge为读后写,非原子测试现状
AevatarUri、InMemoryContextStore、检索主流程、记忆提取与去重决策。LocalFileContextStore真实文件系统场景的集成测试与边界安全测试。