Root cause: a large write_file `content` arg is truncated (finish_reason=length)
before its bytes are emitted, so parse_partial_json drops the key and every retry
re-raises "content: Field required", spinning to recursion_limit=200 (~19min). Not
a model-can't-count-2-args problem — the huge tool argument gets cut off/parsed away.
- L2: detect finish_reason=length + a truncated tool call in the resilience
middleware, nudge to write in smaller parts, bounded retry (truncation_retry_limit).
- L3: new LinsightToolLoopBreakerMiddleware — soft corrective hint then hard stop
(aafter_model raises LinsightToolLoopError) on consecutive same-tool failures,
with guards against aborting a model that recovered / switched tools.
- L4: classify GraphRecursionError / LinsightToolLoopError as TASK_ABORTED.
- Salvage: _handle_task_partial renders the intermediate analysis + retrieved
knowledge as a normal (COMPLETED) result instead of a raw recursion error;
shared across the fresh/resume/continue drivers via _stash_partial_abort.
- Config (LinsightConf): tool_failure_soft_limit=3 / tool_failure_hard_limit=8 /
truncation_retry_limit=2.
72 unit tests + 1 integration test (aafter_model raise propagates through a real
create_agent().ainvoke(), beating recursion_limit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-enable the F035 task-mode Skill runtime that was disabled 2026-06-16. Instead of
the dormant TenantSkillsMiddleware runtime whitelist, use a copy-time gate (Fork X):
at task startup materialize_session_skills copies the run's allowed bundles
(governance-enabled ∩ user-selected) into the session workspace /skills/ subtree, then
a plain deepagents SkillsMiddleware (FilesystemBackend over the workspace cache, which
is dir-aware unlike the MinIO WorkspaceBackend.ls) enumerates them; the model reads the
same /skills/<name>/SKILL.md paths back through the workspace. The copy is the whitelist
gate, so unselected skills never reach the agent — no per-run active_skills config.
- skill_provisioning.materialize_session_skills (new) + SkillStore.read_bytes
- thread skills: LinsightQuestionSubmitSchema + linsight_session_version.skills (JSON col
+ migration f035_linsight_skills) + workbench_impl + task_exec._create_agent
- agent_factory attaches SkillsMiddleware when skills_present
- TenantSkillsMiddleware retired to dormant (docstring); whitelist semantics moved here
- fix(client): selecting a skill in /c task-mode no longer toggles task mode OFF
- tests: test_skill_provisioning (gate, byte fidelity, cross-tenant, enumeration loop)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the implicit-cursor todo attribution with a single segment stream cut
at write_todos calls (B2 single-bucket), so parallel/loose todo marking can no
longer mis-attribute or strand steps in empty "running" todos.
Backend (stream_event_mapper.py):
- stamp all main-graph steps (tool-start / thinking / interrupt) to ctx.svid
- delete the current_in_progress_task_id cursor (_refresh_in_progress + its
_diff_todos call + the StreamContext field); _diff_todos / _status_transition
/ GenerateSubTask are untouched (TaskPanel stays status-driven, zero regression)
- agent_factory.py: drop the "single in_progress at a time" prompt rule
Frontend:
- stepUtils: write_todos becomes the segment boundary (kept through merge, only
namespaced subagent-internal ones dropped); buildTimelineGroups flushes on it
- R1: segment titles are activity summaries + elapsed (com_linsight_act_summary)
- R3: full subagent flattening — explodeSubagentGroup renders each subagent as
its own top-level segment; remove SubagentTeamGroup / SubagentTrack
- R2: degraded narration (in-segment thinking last sentence), collapsed-only
TaskStepRow self-nullifies on empty history, so the existing
ExecutionTimeline(sessionSteps) becomes the single stream with no carrier
changes and free backward-compat for old reload data.
Tests: test/linsight 327 passed; Execution/* 39 passed; tsc clean on touched files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Since Python 3.11, f-string formatting of a str-mixed Enum member returns
str(member) (the repr 'ClassName.MEMBER') instead of its value. This
corrupted every telemetry event_data key into e.g.
'BaseTelemetryTypeEnum.APPLICATION_PROCESS_app_id' instead of the expected
'application_process_app_id'.
Use event_name.value explicitly when building the per-field keys. Add
regression tests asserting key prefixes, and sync docs to Python 3.11
(pyproject already pins requires-python >=3.11).
Increment 1 of the execution-flow render optimization (design docs under PRD/).
Backend (B2): the main-graph `task` call becomes the delegation row —
step_type=subagent, name from tc.args.subagent_type (default general-purpose),
call_reason/extra_info.delegate_goal from description. Namespaced subagent-internal
tools keep their real tool/knowledge type (B1 already landed upstream). Rewrites
the subagent-reintroduction tests + step_types fixture to match.
Frontend: buildFlowNodes groups subagent-internal steps by distinct subgraph
namespace with lazy team-group creation — 22 mislabeled "delegate <tool>" rows
collapse to the 3 real subagents, with no empty-group crash during streaming.
mergeAdjacentThinking stitches token-delta thinking into one passage; firstLine
row titles fix the hardcoded English "thinking"; MergedStep reads frame.timestamp;
SubagentRow shows "N tools · M thoughts" (was counting thinking as tools); the
node build is memoized; tailwind gains the thinking-appear keyframe; +7 i18n keys
across 3 locales.
Verified: jest 14/14, pytest test/linsight 306 passed; deployed and confirmed on
114 (22→3, no crash, correct tool counts; DB: subagent=3, namespaced web_search
all step_type=tool, task frames step_type=subagent/general-purpose).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single merged analysis of the linsight task-mode execution chain (submit ->
worker -> create_deep_agent -> astream) and how its context engineering
compares to the deepagents demo. Serves as the basis/background for the #1
subagent re-introduction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-enable the deepagents `task` tool with a single "general-purpose"
researcher subagent, defusing both original root causes by construction
(not by stripping the tool):
- HITL bubbling (root cause B): the subagent spec carries an explicit tools
list without ask_user and no permissions/interrupt_on, so it has NO
interrupt source — a subagent can never park, so the bug cannot recur.
- over-delegation (root cause A): depth-1 (subagent has no nested task) plus
a delegation-budget prompt section.
agent_factory.py
- drop _disable_subagent_delegation + _ToolExclusionMiddleware({"task"})
- add _subagent_tools blacklist (_SUBAGENT_TOOL_DENY/_KNOWN_HITL_TOOL_NAMES)
and _build_researcher_subagent (name="general-purpose" same-name override)
- add 派发预算 section to LINSIGHT_SYSTEM_PROMPT_ZH + LINSIGHT_RESEARCHER_PROMPT_ZH
stream_event_mapper.py
- drop namespaced (subagent) todos from the main plan (if ns: continue)
- _infer_step_type DORMANT -> LIVE
stepUtils.ts (+ test, + fixture)
- read the subgraph namespace from extra_info.namespace (real backend
contract) so subagent steps fold under their parent task step
task_exec.py
- resume path passes allowed_knowledge_ids: restores the parked tool/subagent
topology and closes the C4 knowledge-whitelist bypass on resume
Tests: test_subagent_reintroduction.py (4) + stepUtils.test.ts (4), green.
Design doc: 灵思任务模式 #1 子代理重引入技术方案.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move the task-mode menu grant (WEB_MENU linsight_task_mode) and the
default-model config migration into idempotent domain backfills invoked from
main.lifespan (mirrors the F034 relation-model pattern; failure only logs and
never blocks startup, self-heals on next boot). The two standalone scripts are
now thin dry-run/apply CLIs over the shared domain logic.
- SOP→Skill migration no longer calls the LLM: a missing skill description falls
back to the SOP name (description is mandatory — NOT NULL + rejected empty by
SkillService), and the --no-llm flag is removed.
- Update the F035 upgrade checklist (08-deployment) + scripts/README: steps 2/3
auto-run at startup, step 4 (SOP→Skill) stays a manual ops script.
- Add unit tests for both backfill pure transforms.
- main.py also picks up incidental ruff-format normalization (quotes/imports).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DaMeng selects the active schema through the dmPython 'schema' connect
kwarg (default = login user). SQLAlchemy maps a URL path (/SCHEMA) to the
'database' kwarg, which dmPython rejects, so the old code stripped the path
for the sync engine only -- making sync (login user) and async (path schema)
diverge.
Replace _dm_sync_url with _normalize_dm_url: at construction, move any
path- or query-specified schema into ?schema= and clear the path, so the
sync (dmPython) and async (dmAsync) engines share one normalized URL and
resolve to the same schema. Explicit ?schema= wins over the path.
Verified live (sync+async) against DM and with unit tests. Documented the
?schema= convention in deployment docs and the config template.
Drop archived v2.5 PRDs, review-process notes, cleanup-task lists and
legacy SDD plans/specs that are no longer referenced.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add PRD, technical design (adapter layer), and drawio flow diagrams
for migrating Linsight from the self-built ReAct engine to the
deepagents/Skill framework.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the per-user in-flight ceiling (max_per_user_inflight) with a
global per-queue concurrency cap as the only hard limit, plus weighted
least-in-flight backfill for fairness.
- settings: queue_concurrency (per-queue cap) + per_user_pick_size
(fairness weight); drop max_per_user_inflight/limit_for.
- run_dispatch_round: fill each queue up to its cap by repeatedly serving
the user with the smallest in_flight/weight, so a single user can
saturate a queue and a freed slot goes to the most-starved user (not the
longest queue).
- lua: DISPATCH_ONE drops the per-user limit; new CONFIRM_DISPATCH bumps a
per-queue inflight_total counter + records inflight_queue mapping;
COMPLETE_FILE returns the slot to the right queue.
- reconcile: authoritatively recompute per-queue counters from the
inflight_queue map to heal drift; purge_file/release_file return slots.
Tests: config, dispatch (fairness/weight/OCR dual-queue/saturation/D3
refill), reconcile recompute, plus Redis-backed Lua + e2e integration
tests against the configured Redis.
Expose pure chunks retrieval as an OpenAPI surface so external systems
(e.g. DeepAgents-style agents bringing their own LLM) can use BiSheng
knowledge bases as a retrieval tool without engaging the SSE RAG path.
- POST /api/v2/filelib/retrieve, authenticated as default_operator
- Supports multiple knowledge bases in one call with per-KB tag filters
(ANY mode; ALL reserved for future use)
- Each returned chunk is tagged with knowledge_id for source attribution
- Reuses _build_folder_search_kwargs + KnowledgeRetrieverTool (RRF +
primary-version filter) from KnowledgeSpaceChatService
- 13 unit tests covering input validation, multi-KB merge, top_k
truncation, tag-name resolution, and KB-not-found
- API reference at docs/api/filelib-retrieve.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Security fixes:
- approval_gate: raise ApprovalScenarioDisabledError directly when scenario
not configured or disabled; stop creating scenario_disabled exception instances
- approval_center_service: add tenant_id isolation check in get_task_detail and
get_instance_detail to prevent cross-tenant data access
- approval_center_service: add approver identity check + task status idempotency
guard in decide_task to prevent unauthorized decisions and double-processing
- resubmit_instance: create a new approval_instance instead of mutating old task
records; preserves audit history and matches spec AC-21
Error codes (errcode/approval.py):
- Add 18104 ApprovalHandlerNotRegisteredError, 18105 ScenarioDuplicate,
18106 ScenarioDisabled, 18107 RouteNotMatched, 18108 ApproverEmpty,
18109 DuplicatePending to align with spec §6.4
- Move former 18104 ApprovalSettingsPermissionDeniedError to 18112
Missing admin APIs (approval_admin.py + service + repository):
- Add DELETE endpoints for scenario, route, flow, node
- Add PATCH /routes/reorder for branch ordering
- Add PUT /flows/{id}/nodes for full-node-list submission (triggers new version)
- Add GET /flows/{id}/versions/{version_id} for flow preview (AC-35)
- Fix _ensure_admin to raise HTTPException(403) instead of bare PermissionError
Other fixes:
- approval_user.py: pass operator_user_name in withdraw call so action_log records
the operator name instead of None
- approval_instance.py: remove SCENARIO_DISABLED from ApprovalExceptionType
- approval_exception_service.py: remove dead retry_scenario_disabled branch/method
Docs & spec alignment:
- PRD: update gate return types; disabled scenarios return error, not PASS
- spec.md: add AC-33/34/35, missing API endpoints, error codes 18104-18109,
test file entry; update AD-08, AC-02, AC-17 for new disabled behavior
- tasks.md: split T002→T002A/B, fix AC range notation, add T004 dep to T006,
split T010A→test+impl, split T015→A/B/C, T016→A/B/C, add T017
Tests: update three test files to match new behavior (58 tests all pass)