[codex] Add tool-driven Aevatar core invocation sources#830
Open
eanzhao wants to merge 6 commits into
Open
Conversation
Records the architectural decision to collapse ChatRouteAction to Reject + ForwardToModel, exposing GAgent/Team/Workflow invocation as IAgentToolSource tools through the existing ToolCallLoop. Supersedes ADR-0024 §D5 (v1 action set) and ADR-0025 (voice v1 ForwardToGAgent); ADR-0024 D1/D2/D3/D4/D6 stand. Tracked end-to-end in epic #808; voice GA prerequisite in #809. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements ADR-0026 Stage 1 unit-1 (epic #808). New project src/Aevatar.AI.ToolProviders.AevatarInvocation/ exposes aevatar_invoke_gagent / _invoke_team / _start_workflow / _observe_run / _query_readmodel as IAgentToolSource, so the LLM can drive orchestration through the existing ToolCallLoop instead of parallel router branches. Design: - Tool payloads are proto-derived strict JSON-Schema (no map<string,string> bags) - wait=ack|stream|complete supported; stream is default for long-running tools; GAgent/workflow wait=complete returns wait_complete_unavailable until Stage 2 session actor lands - Caller scope flows through AgentToolRequestContext only; protected caller-scope keys (LLMRequestMetadataKeys.*) are stripped from LLM-supplied payload.headers before server values are stamped, so the LLM cannot inject overrides for nyxid.access_token / scope_id / owner_subject etc. - query_readmodel is bounded to a closed registered set - Dispatch reuses existing surfaces (IActorDispatchPort, ITeamEntryMemberResolver + IStaticGAgentStreamInvocationPort<AGUIEvent>, ICommandDispatchService<WorkflowChatRunRequest,...>); no new dispatch chain 21 tests pass (4 credential-injection regression + 1 ObserveRun fast-fail added in post-review hardening); arch_guards + test_stability + docs lint all PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements ADR-0026 Stage 1 unit-2 (D7 prerequisite) for the Lark
outbound caller-scope guarantee. After auditing the existing path
(LarkMessagesSendTool → LarkNyxClient → NyxIdApiClient) no production
refactor was required: the tool already reads
AgentToolRequestContext.NyxIdAccessToken (no credential parameters) and
forwards the caller bearer through NyxID's api-lark-bot proxy, which
exchanges to a Lark tenant_access_token without seeing the caller's
authorization header. The metadata-bag credential-injection surface that
unit-1 had to harden is structurally absent here (no headers/metadata
bag at the dispatch boundary).
Added 2 regression tests:
- Asserts the dispatched NyxID call carries AgentToolRequestContext's
trusted typed NyxIdAccessToken
- Asserts a malicious LLM payload (smuggled nyx_id_access_token, fake
headers, ExternalMetadata overriding LLMRequestMetadataKeys.NyxIdAccessToken)
cannot override the trusted caller token at dispatch
NyxID investigation summary (verified via gh against ChronoAIProject/NyxID
backend source): /api/v1/proxy/s/api-lark-bot/open-apis/im/v1/messages
accepts only the caller's NyxID bearer; NyxID resolves caller's
api-lark-bot binding, exchanges {app_id, app_secret} → tenant_access_token
per channel_adapters/lark.rs::lark_family_token_exchange_config, strips
the inbound authorization, and injects bearer for outbound to Lark.
Semantic: messages post as the caller's bound Lark bot (NyxID-mediated),
not as the human user's OAuth identity and not as Aevatar's service-level
identity. This satisfies ADR-0026 §D7's "lands in the caller's Lark
account" use case.
61/61 tests pass; arch_guards + test_stability + docs lint all PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes ADR-0026 Stage 1 (epic #808). Integration test demonstrates the new tool-first ingress path works end-to-end after units 1+2 landed, without touching any production code. Test: MainnetResponsesEndpointsTests.PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope Scenario: - /v1/responses streamed request with real DI registration of unit-1's AddAevatarInvocationTools (5 production IAgentToolSource instances) - Stubbed LLM emits aevatar_invoke_gagent tool call with a malicious payload that smuggles nyxid.access_token + aevatar.scope_id overrides - ResponsesCompletionApplicationService executes the local tool call inline (not as function_call SSE output — verified against production StreamAsync behavior) - AevatarInvocationDispatcher dispatches through IActorDispatchPort (captured by RecordingActorDispatchPort) - LLM round 2 continues after tool result, SSE lifecycle completes Assertions: - Dispatched envelope's Route.PublisherActorId == DirectGAgentPublisherId - Dispatched ChatRequestEvent.Headers carry the trusted bearer/scope (caller-scope protection from unit-1 verified end-to-end) - ThrowingStaticGAgentStreamInvocationPort.InvocationCount == 0 (the legacy ForwardToGAgent/ForwardToTeam path in ResponsesEndpoints.cs:779-927 is NOT entered) 202/202 tests pass in Aevatar.Hosting.Tests; arch_guards + test_stability_guards + docs lint all PASS. No production code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## dev #830 +/- ##
==========================================
+ Coverage 83.06% 83.07% +0.01%
==========================================
Files 981 981
Lines 61936 61936
Branches 8069 8069
==========================================
+ Hits 51447 51454 +7
+ Misses 7009 6996 -13
- Partials 3480 3486 +6
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
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.
背景
这个 PR 是 ADR-0026 的 Stage 1:把 aevatar 的核心能力重新定位为 LLM tool source,让模型通过 function call 主动选择何时使用 workflow、GAgent、team、readmodel observation 等能力,而不是继续在入口层维护
ForwardToGAgent/ForwardToTeam这类并行路由方言。改动
docs/adr/0026-tool-first-chat-ingress.md,明确 tool-first chat ingress 的目标、边界和后续阶段。Aevatar.AI.ToolProviders.AevatarInvocation,提供 5 个 invocation tools:aevatar_invoke_gagentaevatar_invoke_teamaevatar_start_workflowaevatar_observe_runaevatar_query_readmodelAevatarInvocationDispatcher,统一做 proto 参数解析、caller scope 注入、调度、readmodel 查询与结构化错误返回。IAgentToolSource发现链路。AgentToolRequestContext.NyxIdAccessToken,payload/外部 metadata 不能覆盖调用者凭据。/v1/responsesE2E 测试,证明模型发出的aevatar_invoke_gagentadditive tool call 会走 tool loop 并通过IActorDispatchPort投递 actor envelope,而不是走 legacyForwardToGAgent静态调用链路。影响
wait=complete仍返回结构化wait_complete_unavailable;当前阶段支持ack/stream,后续由 session actor/观察链路承接长任务 continuation。aevatar_query_readmodel只允许查询封闭集合 readmodel,不开放任意 document collection。验证
dotnet test test/Aevatar.AI.ToolProviders.AevatarInvocation.Tests/Aevatar.AI.ToolProviders.AevatarInvocation.Tests.csproj --nologo:通过,21 passed。dotnet test test/Aevatar.AI.ToolProviders.Lark.Tests/Aevatar.AI.ToolProviders.Lark.Tests.csproj --nologo:通过,61 passed。dotnet test test/Aevatar.Hosting.Tests/Aevatar.Hosting.Tests.csproj --filter FullyQualifiedName~PostResponses_StreamWithAevatarInvokeGAgentAdditiveTool_ShouldDispatchActorEnvelope --nologo:通过,1 passed。bash tools/ci/test_stability_guards.sh:通过。bash tools/ci/architecture_guards.sh:通过。git diff --check origin/dev..HEAD:通过。备注:本地测试仍有既有 NuGet source mapping / analyzer warnings,没有测试失败。