Skip to content

Fix/protocol routing governance lemonc19 - #107

Merged
iancaoo merged 19 commits into
datagallery-lab:mainfrom
LemonC19:fix/protocol-routing-governance-lemonc19
Aug 17, 2026
Merged

Fix/protocol routing governance lemonc19#107
iancaoo merged 19 commits into
datagallery-lab:mainfrom
LemonC19:fix/protocol-routing-governance-lemonc19

Conversation

@LemonC19

@LemonC19 LemonC19 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #100.

本 PR 修复了多轮对话中协议路由丢失任务语义的问题,并将修复延伸到后续执行链路:

  • 用版本化 Session Intent 保存会话当前的真实任务;
  • 路由同时判断运行协议和当前消息与原任务的关系;
  • 将 protocol snapshot、route events 和 run intent binding 原子提交;
  • 在 handoff 时原子切换 protocol segment 和 Session Intent;
  • 用动态 Tool Plan 表达当前协议权限,Runtime guard 仍作为最终授权边界;
  • 防止被协议拒绝的数据任务被一段结束文本标记为 completed。

本 PR 不声称消除所有 LLM 误路由。确定性续接、持久化任务版本、并发冲突和执行权限由 Runtime 保证;其他歧义输入仍依赖 classifier,具体边界见“已知限制”。

问题与根因

DataFoundry 会在每次运行前选择 general-taskdata-analysis 协议。原实现只根据当前 userInput 路由,没有持久化“这个会话正在处理什么任务”。

因此,用户先发起数据分析,之后输入“再次尝试”时,会发生:

  1. 路由器只看到“再次尝试”,低置信度后回落到 general-task
  2. 主 Agent 从对话历史中理解用户想继续分析,因而尝试调用数据工具;
  3. general-task 的 phase gate 拒绝这些数据 action;
  4. 即使数据分析没有执行,运行仍可因 Agent 输出了一段失败说明而被标记为 completed。

这不只是 classifier 缺少上下文,而是路由器、主 Agent 和协议执行层没有共享同一份权威任务状态。只向 classifier prompt 追加历史可以降低误分类概率,但无法保证重试、并发运行、handoff 和 session branch 使用正确的任务版本。

运行时链路

一次新运行现在按以下顺序进入主 Agent:

server.ts
  └─ resolveSessionIntentForRun()
       读取该 run 已绑定的 revision,或 session/branch 当前可见的 revision

createRunProtocolBoundary()
  ├─ ProtocolRouter.route()
  │    explicit/restored → 纯续接继承 → 新任务分析加速规则 → classifier → default
  ├─ effectiveIntentText()
  │    根据 continue/refine/replace/side-chat 生成本次有效任务文本
  ├─ requirementExtractor()
  │    仅对 data-analysis 执行,输入是有效任务而非弱后续文本
  └─ ProtocolRuntime.start()
       创建初始 protocol snapshot 和 route/start events

MetadataProtocolStateStore.create()
  └─ BEGIN IMMEDIATE
       ├─ 写入 protocol snapshot + journal events
       ├─ commitSessionIntentFromProtocolStartWithinTransaction()
       └─ 写入 Intent Revision / Session Head / Run Binding

createDataFoundry()
  └─ 在已提交的 protocol 和 intent 上构造并运行主 Agent

这条链路保证主 Agent 在开始执行工具前,本次运行使用的 protocol 和 Intent Revision 已经固定。

代码入口

职责 主要实现
Session Intent 版本提交与 run binding apps/api/src/session-intent.ts
Intent 表结构、CAS 和 branch revision 解析 packages/metadata/src/index.ts 中的 SessionIntentRepositoryinitializeSessionIntentSchema
Protocol 路由、task relation 和 effective intent packages/agent-runtime/src/protocol/protocol-router.tsrun-protocol-boundary.ts
Handoff 校验与 segment 切换 packages/agent-runtime/src/protocol/protocol-handoff-coordinator.ts
Protocol/Intent 原子持久化 apps/api/src/protocol-state-store.tspackages/metadata/src/index.ts 中的 transitionSegment
工具组装与动态可用性 packages/agent-runtime/src/tools/tool-plan.ts
Helper context 预算与裁剪 packages/agent-runtime/src/context/helper-context.ts
Run 终态硬门 apps/api/src/protocol-run-completion.ts

主要改动

1. 版本化 Session Intent

设计

Session Intent 表示“会话当前正在处理的任务”,而不是最近一条消息。例如:

protocol: data-analysis@1
intent: 分析各地区销售额变化,并解释下降原因

用户说“谢谢”时,原任务仍然保留;用户补充“按季度拆分”时,它成为原任务的新 revision;用户改为“帮我写 README”时,创建新的 task identity。

实现

Migration 0018_session_intents 新增三张表:

  • session_intent_revisions:不可变的任务版本,保存 intent_idprevious_revision_id、protocol、intent text、change kind 和 source run;
  • session_intent_heads:每个 session 当前指向的 revision;
  • run_intent_bindings:每次运行启动前的 base_revision_id、路由后的 active_revision_idtask_relation

SessionIntentRepository.commitWithinTransaction() 提交新 revision 前会比较 expected_head_revision_id 与数据库当前 head。两个并发运行基于同一版本更新任务时,只有第一个可以提交;另一个以 SESSION_INTENT_REVISION_CONFLICT 在主 Agent 执行前失败。

恢复已持久化的 protocol state 时,API 还会要求该 run 存在对应的 intent binding。如果只找到 protocol state 而找不到 run_intent_bindings 记录,则以 RUN_INTENT_BINDING_REQUIRED 拒绝恢复,不允许 Runtime 在缺少确切任务版本时根据 session 最新状态猜测。

commitSessionIntentFromRouteWithinTransaction() 根据 task relation 完成具体写入:

  • continue:沿用 task identity 和 intent text;protocol 不变时不创建 revision,protocol 改变时创建 route-switch revision;
  • refine:沿用 task identity,将当前输入作为补充要求追加,创建 refine revision;
  • replace:使用当前输入创建新 task identity 和 replace/initial revision;
  • side-chat:不更新 session head,run 仍绑定启动时的 base revision。

恢复和分支语义

  • RunCheckpointProjector 将当前 run binding 的 active_revision_id 写入 checkpoint;
  • 从 checkpoint 创建 branch 时,使用 checkpoint 的 intent_revision_id
  • 从历史 run 重写时,使用目标 run 的 base_revision_id,因为分支点在该 run 之前;
  • resolveForSession() 在 child 没有自己 head 时,只读取 fork_intent_revision_id,不沿 lineage 读取父 session 的当前 head。

因此,父会话在 fork 后切换任务,不会改变已创建 child 的任务语义。

2. Protocol 路由与任务关系分离

设计

general-task / data-analysis 回答“这次应该怎么运行”;continue / refine / replace / side-chat 回答“这条消息是否改变原任务”。两者不能用一个分类结果代替。

实现

ProtocolRouteClassificationProtocolRouteResult 新增 taskRelationcreateModelProtocolClassifier() 使用严格 Zod schema 解析一次模型输出,同时获取 protocol、confidence、reason codes 和 task relation,不新增第二次分类调用。

ProtocolRouter.route() 按下列优先级解析:

  1. 已持久的 protocol segment:以 PROTOCOL_SEGMENT_RESTORED + continue 恢复;
  2. 用户显式选择 protocol:纯续接时继续原任务,否则按新任务处理;
  3. 已有 Session Intent 且当前输入是纯弱续接:以优先级 300 产生 SESSION_INTENT_INHERITED + continue,不调用 classifier;
  4. 无 Session Intent 且命中分析加速规则:以优先级 100 产生 ANALYTIC_INTENT + replace
  5. 其他输入:调用 classifier;
  6. classifier 失败或置信度低于 0.75:回落 general-task + side-chat,记录 warning 且不覆盖原任务。

weakContinuationIntent() 要求整条消息仅由“再次尝试 / retry / continue”等续接语及标点组成,并设有 24 字符上限。因此“继续帮我写 README”会进入 classifier,不会直接继承原任务。

analyticIntent() 只在没有 active intent 时参与路由,并移除 salescountorders 等单独命中就会导致误路由的词。它不再控制需求抽取或语义校验。

effectiveIntentText() 在路由后计算本次有效任务。Requirement extractor 和 semantic.context.resolve 均使用这段文本;从 general-task handoff 到 data-analysis 时,Runtime 会补做 requirement extraction,使新 segment 不会以空分析要求开始。

3. Protocol 状态与 Intent 的原子 Handoff

设计

Handoff 不只是换一个 protocol ID。它还会结束当前 segment、创建新 segment、更新任务使用的 protocol,并让同一 Agent 立即获知新权限。这些写入必须同成功或同失败。

实现

ProtocolStateStore.transitionSegment() 增加可选的类型化 intentTransitionMetadataProtocolStateStore 将其交给 Metadata repository,后者在一个 BEGIN IMMEDIATE 事务中:

  1. 以 expected revision CAS 更新旧 segment 为 handed_offaborted
  2. 以 expected revision -1 创建新 active segment;
  3. protocol_event_journal 写入 proposed、segment-ended、accepted 和 segment-started 事件;
  4. applyIntentTransition() 校验 session head 仍等于当前 run binding 的 active revision;
  5. 创建 handoff Intent Revision,更新 Session Head 和 Run Binding;
  6. 提交事务。

任一 protocol CAS、Intent CAS、revision 或 journal 写入失败,整个 accepted handoff 回滚。被拒绝的 proposal 也会通过 compareAndSetWithEvents() 持久化 proposed/rejected events,供事件回放和审计。

普通 run 的 handoff 沿用当前 task identity 和 intent text,只改变 protocol;如果当前 run 是 side-chat 或没有 active intent,那么 handoff 表明当前消息已成为实质任务,Runtime 会用当前 user input 创建新 task identity。

ProtocolHandoffCoordinator 不信任模型上报的 unresolvedGoals。它调用当前 protocol definition 的 completionPolicy() 从服务端状态计算未完成项;data-analysis 仍有严格目标未完成时,普通 handoff 不能离开该协议。原 unresolvedGoals 字段仅保留为模型 schema 兼容和审计输入。

另外,对 classifier 产生的 replace 路由,如果 data-analysis 尚在初始 phase、目标是 general-task、且 action ledger 中没有成功数据 action,则允许发起 route-correction。旧 segment 以 aborted 结束,并写入 PROTOCOL_ROUTE_CORRECTED

Handoff 完成后,handoffObservation() 会基于新 segment 重算工具可用性,将 activeProtocolIdactivePhaseavailableToolsprotocolDisabledTools 返回给同一 Agent。

4. Tool Plan、Runtime Guard 与终态硬门

设计

工具 schema 需要保持静态,以支持同一 Agent 在 handoff 后继续运行;但“模型看得到某个工具”不等于“当前协议允许调用它”。因此本 PR 将工具组装、当前可用性和最终授权分层处理。

实现

buildToolPlan() 统一了原来分散的工具组装顺序:

  1. 合并 data、file、knowledge、task、collaboration、workspace 和 skill 工具组;
  2. 先应用 skill deny,再应用 allow,保留 always-allow 和 skill-meta 例外;
  3. 在 skill policy 之后合并由独立 server allowlist 管理的 MCP tools;
  4. 显式加入 protocol_handoffanalysis_requirements_commit

每个 ToolPlanEntry 记录 sourceexposed、reason trail、availability: available | protocol-disabled 和可选 handoff recovery target。

resolveToolPlanAvailability() 将静态工具名映射为 protocol action(例如 protocol_handoff → protocol.handoff.propose),并与当前 phase 的 allowedActions 对比。createDataFoundry.toolPlan 以 getter 形式每次根据当前 protocol state 重算 snapshot。

Tool Plan 用于 diagnostics、Agent instructions 和 handoff observation,不作为授权凭据。所有受治理的工具最终都通过 ActionRouterProtocolRuntime.assertActionAllowedInState();即使模型尝试调用 protocol-disabled 工具,也会在实际执行前被拒绝。

ACTION_NOT_ALLOWED_IN_PHASE 现在会在错误第四段和结构化 error.details.allowedActions 中返回当前 phase 的可用 action,使 Agent 能从拒绝中选择恢复路径,而不是继续盲目试探。原三段式前缀保持兼容。

completeProtocolRun() 在通用回答提交之前检查 protocol action ledger。如果 general-task 存在因 ACTION_NOT_ALLOWED_IN_PHASE 被拒绝的数据 action,它会:

  • 调用 ProtocolRuntime.terminateFailure() 持久化 failed terminal decision;
  • 调用 RunFinalizer.fail() 将 metadata run 标记为 failed;
  • 返回 DATA_ACTIONS_REJECTED_BY_PROTOCOL,不允许一段失败说明将该运行转为 completed。

时间预算耗尽等原有 partial completion 语义未改变。

5. Helper Context:只为歧义分类提供历史

设计

Session Intent 是路由器可以直接依赖的权威任务状态;对话摘要、记忆和近期查询可能由模型或用户生成,只能作为歧义判断的参考。两类上下文不能拥有相同权限。

实现

API 在组装 Agent 前调用 buildHelperContext(),收集:

  • 当前 Session Intent;
  • 最近两条可见、非合成 summary、且不包含当前输入的用户查询;
  • Metadata 中最新的 conversation summary;
  • 本次召回的最多三条 long-term memories。

最终 block 受 DATAFOUNDRY_HELPER_CONTEXT_MAX_CHARS 限制,默认为 2000 字符。超限时按以下顺序删除 section:

relevant memories → conversation summary → recent queries

Session Intent 不会作为 section 被删除;如果只剩它仍超限,则在字段内截断。构造器始终保留 header/footer,并转义历史内容中伪造的边界 marker。

该 block 只通过 classificationInput.background 传给 protocol classifier,不参与确定性候选的生成,也不直接更新 Session Intent。它是 budgeted untrusted context,边界 marker 用于向模型说明内容属性,不是 prompt injection 的强安全边界。

Session title 不直接消费这份 helper block;它在 route/intent commit 后重新读取 active intent text 作为标题输入。因为 side-chat 不更新 head,一次闲聊不会覆盖原任务标题。

已知限制

  • Classifier 仍然可以误判。 确定性规则覆盖了恢复 segment 和纯重试/续接,其他歧义消息仍可能被错分为 continue、refine、replace 或 side-chat。当前尚没有在标注的多轮路由数据集上报告准确率。
  • route-correction 仍信任同一 Agent 发起纠错。 服务端限制了来源路由、目标协议、初始 phase 和已执行 action,但没有引入第二个 classifier、独立 permit 或用户确认。误纠正可能使尚未开始执行的有效分析任务切回 general-task,但会留下 aborted segment 和 handoff events。
  • Completion hard gate 只覆盖已经发生的协议拒绝。 如果 Agent 误路由后完全不尝试数据工具,而是直接提交普通回答,当前 Runtime 不会对用户原始任务再做一次独立语义验收。
  • Intent text 仍是有上限的扁平文本。 最长 2000 字符,refine 通过“原任务 + 补充要求”追加后截断,尚未实现结构化 requirement merge 或超长任务的语义压缩。
  • Helper context 是字符预算下的固定策略。 它不是 token 精确分配,也不会对 summary/memory 做动态相关性重排。
  • 工具 schema 在单次运行内不会重建。 模型仍能看到 protocol-disabled 工具;动态 Tool Plan 和 observation 负责引导,实际调用由 Runtime guard 拒绝。
  • Handoff 的事务范围是本地 Metadata SQLite。 它保证 protocol state、event journal 和 intent binding 一致,不是跨外部数据源或工具执行的分布式事务。
  • 本次升级不支持恢复旧版本已持久化的暂停任务。 Migration 0018_session_intents 不会为升级前的 run 回填 intent binding。因此,升级前因 ask_usersubmit_plan 等流程处于 suspended 状态的 run,升级后恢复时会因“已有 protocol state、但没有 run intent binding”触发 RUN_INTENT_BINDING_REQUIRED。这是当前项目不承诺跨版本恢复下的有意版本边界;升级前应完成或取消仍处于 suspended 状态的任务,并在 vNext release/upgrade notes 中同步说明。

验证结果

验证项 结果
npm run build 通过
npm run test:packages 通过:46 个文件,258 个测试
npm run test:web 通过:60 个文件,586 个测试
npm --workspace @datafoundry/tui test 未全部通过:101 个测试中 100 通过、1 失败;guard 扫描到本地 ignored 文件 docs/engineering/2026-08-03-subsystem-4-apps.md 中的 offline demo 字符串,该文件不在本分支 diff 中
npm run smoke:sql 通过
npm run smoke:agui-stream 通过
npm run smoke:agent 失败:现有 smoke 在 artifact event must be a slim reference 断言处失败;该断言不覆盖 #100 的 protocol routing 路径
smoke:agent-protocol-deepseeksmoke:task-state 未运行:需要有效模型密钥;关键状态路径有 stubbed unit tests,但不等价于真实模型端到端验证

与本 PR 直接相关的新增或加固回归场景包括:

  • 纯弱后续不调用 classifier 并继承 data-analysis
  • “继续帮我写 README”不命中弱续接;sales pitchcount files 不被分析加速规则强制路由;
  • classifier 严格解析 task relation,低置信度时不覆盖原 intent;
  • initial route commit 冲突时,protocol snapshot、journal events 和 run binding 同时回滚;
  • run rewrite、checkpoint branch、child 和 grandchild 固定正确 Intent Revision,parent 后续换任务不泄漏到 branch;
  • handoff 原子更新 intent,Intent CAS 冲突时旧/新 segment 和 events 全部回滚;
  • 严格协议 handoff 无法通过空 unresolvedGoals 绕过;accepted/rejected proposals 均可从 journal replay;
  • route correction 保留 aborted segment 和纠错事件,成功数据 action 之后不允许纠错;
  • Tool Plan 在 general-taskdata-analysis 各 phase 及 handoff 后重算 availability;
  • helper context 按预期顺序丢弃 section,截断后保留完整边界并转义伪造 marker;
  • 被协议拒绝的数据任务在 protocol terminal decision 和 run metadata 中均以 failed 结束。
Commit 覆盖对照

upstream/main@08afa7b 为 merge base,当前分支的 18 个非 merge commit 对应如下:

主题 Commits 正文对应
阶段拒绝和 Agent instructions 7eb3e46, cbecfcd 改动 4
Package/API 测试纳入 CI b4b9c32, a4f6237 验证结果
Completion hard gate 1c5ffa7, a4f6237 改动 4
Session Intent 初版、继承路由和需求抽取顺序 a20f202, e005d85, a25e8f0, 915244e, a461575 改动 1、2
Tool Plan 重构 d9a9c51, 8516a0c, c5967eb 改动 4
Helper context 918f127, 671cf5a 改动 5
版本化 Intent、任务关系和 branch binding 573763c 改动 1、2、5
Handoff 和 failure gate 加固 11411f3 改动 3、4
原子 handoff、route correction 和工具权限观测 548fe7a 改动 3、4

新分支按功能 commit 顺序重放,未保留原历史中 5 个不包含额外代码的阶段性 merge commit。

ACTION_NOT_ALLOWED_IN_PHASE previously told the model only what it could
not do, so a mis-routed agent probed tools one by one (observed 4 rejected
calls in issue datagallery-lab#100). The rejection message now carries the current
phase's allowedActions as a fourth segment, and the model-facing
observation surfaces them in error.details.allowedActions plus a concrete
recovery instruction. The message keeps its three-segment prefix, so
existing split(":") parsers and startsWith checks are unaffected.
…uctions

The system prompt advertised every selected data tool regardless of the
governing protocol, while general-task rejects all four data actions at
the phase gate. A mis-routed run therefore told the model it had tools it
could never use. Instructions now state explicitly, under general-task,
that the data tools are disabled by the protocol and that protocol_handoff
to data-analysis is the supported path.

Also introduces protocol/data-actions.ts as the single source for the
governed data action names; DATA_AGENT_TOOL_NAMES re-exports it and the
instructions builder consumes it instead of a hardcoded array. Protocol
definitions migrate to the shared constant in a follow-up refactor.
packages/**/*.test.ts were type-checked by tsc but never executed:
CI only ran the web and TUI suites plus smoke scripts, so protocol-layer
behavior had no unit gate. Adds a root test:packages script (vitest
declared as a root devDependency instead of relying on hoisting) and runs
it in the build-and-web-tests job.
…d data actions

completeProtocolRun auto-committed a general.answer.commit for any
general-task run with assistant text, then forced a terminal decision and
finalized the run as completed. A mis-routed run whose data tool calls
were all rejected by the phase gate therefore ended as a metadata
'completed' even though the datasource was never touched (issue datagallery-lab#100).

The completion path now inspects the protocol action ledger first: when
the governing protocol is general-task and it rejected data actions with
ACTION_NOT_ALLOWED_IN_PHASE, the run records its terminal protocol
decision for replay and finalizes as failed with an explicit
DATA_ACTIONS_REJECTED_BY_PROTOCOL reason. Time-budget and other partial
completions keep their existing completed semantics.
Covers the completion hard gate: general-task runs with phase-rejected
data actions finalize as failed with DATA_ACTIONS_REJECTED_BY_PROTOCOL;
non-data rejections and data-analysis runs keep completing. Extends the
test:packages script to also execute the apps/api vitest suite, which was
previously type-checked but never run.
…olution

Persists the authoritative record of what a session is working on:
{protocol_id, protocol_version, intent_text, source_run_id} keyed by
session. resolveForSession walks up session_branches lineage (bounded
depth) so a branched session inherits its ancestor's task instead of
starting blind — guessing 'the previous terminal run' cannot express
this and breaks on gap runs, branches, and suspended runs. Session
deletion cascades to intents.
…ndidate

Weak continuation follow-ups (再次尝试 / retry / continue …) now inherit
the session's recorded protocol through the existing deterministic
candidate channel at priority 300 — above the keyword accelerator, below
same-run restore — so no model call, low classifier confidence, or
classifier failure can drop the run to general-task while the session is
mid-analysis. For ambiguous non-continuation follow-ups the classifier
still runs, but its input now carries the session intent as context and
its prompt explains how to use it. Weak follow-ups without any recorded
intent keep today's default-route behavior byte for byte.
…xtraction and semantic query with intent text

Requirement extraction previously ran before routing and was gated by
the analyticIntent keyword regex, so a classifier- or intent-routed
data-analysis run started with zero user requirements — the entire
assertion pipeline (contract grounding, SQL semantic validation, result
checks, evidence-bound claims) silently short-circuited, and the
semantic service was queried with the literal follow-up wording.

Protocol definitions now register requirement-free for routing, and
extraction runs after the route resolves to data-analysis (including at
protocol handoff, so a general-task run moving to data-analysis starts
its new segment with a full contract). Both the extractor and the
semantic.context.resolve query consume the effective intent text: the
recorded session intent with the follow-up appended, or the user's own
words whenever they carry a task. The regex no longer gates any
quality-critical path — it remains only a routing accelerator.
Before assembling a run, the server resolves the session's recorded
intent (following branch lineage) and passes it into the protocol
boundary. After routing, strong-signal resolutions — explicit protocol,
keyword accelerator, confident classifier — persist the user's wording
as the session intent. Inherited weak follow-ups, restored segments,
default-route fallbacks, and blank input never overwrite the record, so
'再次尝试' can inherit the task but can never become the task.
…t its role

Adds common English analytic phrasing (revenue, trend, breakdown,
group by, cohort, …) and further Chinese metrics vocabulary to the
keyword accelerator, and documents that it is a routing accelerator
only — after the extraction reorder it gates no quality-critical path,
so a false hit is corrected by session-intent inheritance and a miss
costs one classifier call.
…col definitions

Completes the deduplication started with protocol/data-actions.ts: both
formal protocol definitions now consume the shared set instead of
re-declaring the four governed data action names locally.
analysis_requirements_commit and protocol_handoff hand-rolled the same
ActionRouter execution body (tool-call id, fallback action id,
idempotency key, observation unwrap, error shaping). Both now share
createProtocolBoundExecute, which also documents why segmentId must be
read at call time (handoffs move the active segment).
The run's tool set previously passed through several disconnected steps
(resource-gated spreads, always-allow set, skill allow/deny filter, MCP
merge) with no record of why a tool was in or out. buildToolPlan now
performs the same pipeline in one place with identical semantics — deny
before allow, always-allow and skill-meta exemptions, MCP joining after
the skill policy under its own per-server allowlist — and every entry
carries a reason trail. createDataFoundry returns the plan entries so
diagnostics and audit surfaces can answer 'why does the model (not) see
this tool' without a debugger. Protocol-phase permissions intentionally
stay with the protocol runtime's action gate.
Helper models (protocol classifier, session title) each assembled their
own prompt from a single sentence. buildHelperContext produces one
compact background block — session intent, recent queries, conversation
summary, relevant memories — inside reference-only delimiters so
recorded history cannot act as instructions. The block is hard-capped by
a registered limit (DATAFOUNDRY_HELPER_CONTEXT_MAX_CHARS) and sections
drop in fixed priority order; the session intent is never dropped.
…ssion title

The server assembles a helper-context block from the session intent,
latest conversation summary, and recalled long-term memories, and
forwards it to the protocol classifier as reference-only background for
ambiguous follow-ups. The classifier prompt states explicitly that the
block is history, not instructions. Session titles now prefer the
session's recorded intent text, so a branched session whose first
message is '再次尝试' is titled by its inherited task instead of the
follow-up wording.
Comment thread apps/api/src/server.ts

@wangjk9527 wangjk9527 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@iancaoo
iancaoo merged commit 5cf7d36 into datagallery-lab:main Aug 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Protocol routing drops session context for follow-up queries and blocks data tools

3 participants