langchain-agentx-python 2.3.2__py3-none-any.whl → 2.3.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ from langchain_agentx import create_loop_agent
11
11
  ```
12
12
  """
13
13
 
14
- __version__ = "2.3.2"
14
+ __version__ = "2.3.4"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -4,6 +4,7 @@ from .loop_config import (
4
4
  AgentStateConfig,
5
5
  LoopServices,
6
6
  PromptConfig,
7
+ SkillDiscoveryConfig,
7
8
  TraceConfig,
8
9
  SubagentLinkage,
9
10
  )
@@ -25,6 +26,7 @@ from .runtime_settings import (
25
26
  __all__ = [
26
27
  "AgentLoopConfig",
27
28
  "AgentStateConfig",
29
+ "SkillDiscoveryConfig",
28
30
  "LoopServices",
29
31
  "PromptConfig",
30
32
  "TraceConfig",
@@ -0,0 +1,38 @@
1
+ """Compaction 控制面 env 解析(对齐 CC DISABLE_COMPACT / DISABLE_AUTO_COMPACT)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+
8
+ def _parse_bool_env(name: str) -> bool | None:
9
+ raw = os.environ.get(name, "").strip().lower()
10
+ if not raw:
11
+ return None
12
+ return raw in ("1", "true", "yes", "on")
13
+
14
+
15
+ def resolve_is_auto_compact_enabled(*, config_is_auto_compact: bool = True) -> bool:
16
+ """CC:DISABLE_COMPACT 关全部;DISABLE_AUTO_COMPACT 仅关 proactive autocompact。"""
17
+ if _parse_bool_env("DISABLE_COMPACT") is True:
18
+ return False
19
+ if _parse_bool_env("DISABLE_AUTO_COMPACT") is True:
20
+ return False
21
+ return config_is_auto_compact
22
+
23
+
24
+ def compute_autocompact_trigger_token_threshold(
25
+ *,
26
+ context_window_tokens: int,
27
+ max_output_tokens_reserved: int,
28
+ compact_buffer_tokens: int,
29
+ ) -> int:
30
+ """对齐 CC:effectiveWindow - AUTOCOMPACT_BUFFER_TOKENS。"""
31
+ effective = max(0, int(context_window_tokens) - int(max_output_tokens_reserved))
32
+ return max(0, effective - int(compact_buffer_tokens))
33
+
34
+
35
+ __all__ = [
36
+ "compute_autocompact_trigger_token_threshold",
37
+ "resolve_is_auto_compact_enabled",
38
+ ]
@@ -52,6 +52,16 @@ class PromptConfig:
52
52
  thinking_generation_enabled: bool = True # 是否启用 request-side thinking 生成(对齐 CC thinkingConfig)
53
53
 
54
54
 
55
+ @dataclass(frozen=True)
56
+ class SkillDiscoveryConfig:
57
+ """Workflow / 双 workspace 节点的 Skill 发现层级(对齐 bug.md ComponentScan)。"""
58
+
59
+ project_skills_dir: str | Path | None = None
60
+ global_skills_dir: str | Path | None = None
61
+ include_managed: bool = False
62
+ include_user_home: bool = False
63
+
64
+
55
65
  @dataclass(frozen=True)
56
66
  class AgentStateConfig:
57
67
  """Agent 状态配置(Agent 状态目录落点 / 派生目录视图)。
@@ -64,6 +74,7 @@ class AgentStateConfig:
64
74
  runtime_cwd: 工具运行时 cwd / 相对路径展开基准(None 时回退到 workspace_root)
65
75
  read_roots: 只读根目录
66
76
  write_roots: 只写根目录
77
+ skill_discovery: 可选项目级 / 全局 skill 目录(覆盖 legacy 三源)
67
78
 
68
79
  CC 对照:无直接对应,SDK 特化的状态层配置。
69
80
  """
@@ -74,6 +85,7 @@ class AgentStateConfig:
74
85
  write_roots: tuple[str, ...] = ()
75
86
  workspace_trust_accepted: bool | None = None
76
87
  enable_dynamic_root_grant: bool = False
88
+ skill_discovery: SkillDiscoveryConfig | None = None
77
89
 
78
90
 
79
91
  @dataclass(frozen=True)
@@ -241,6 +253,7 @@ class AgentLoopConfig:
241
253
  token_budget_max_continuations: int = 3
242
254
  compact_buffer_tokens: int = 13_000
243
255
  max_output_tokens_reserved: int = 20_000
256
+ is_auto_compact: bool = True
244
257
 
245
258
  # -----------------------------------------------------------------------
246
259
  # 运行模式
@@ -33,17 +33,33 @@ from .stages.base import CompactionStage
33
33
  from .types import CompactProgress, ContextCompactionContext
34
34
 
35
35
 
36
+ def _apply_autocompact_failure_patch(
37
+ state: dict[str, Any],
38
+ stages_meta: list[dict[str, Any]],
39
+ patch: dict[str, Any],
40
+ ) -> None:
41
+ ac = next((m for m in stages_meta if m.get("name") == "autocompact"), None)
42
+ if ac is None:
43
+ return
44
+ extra = ac.get("extra") or {}
45
+ prev = int(state.get("consecutive_autocompact_failures") or 0)
46
+ if extra.get("autocompact_llm_failed"):
47
+ patch["consecutive_autocompact_failures"] = prev + 1
48
+ elif extra.get("autocompact_mode") == "llm":
49
+ patch["consecutive_autocompact_failures"] = 0
50
+
51
+
36
52
  class DefaultCompactionStages:
37
53
  """CC 五段默认装配(顺序即契约)。"""
38
54
 
39
55
  @staticmethod
40
- def build() -> tuple[CompactionStage, ...]:
56
+ def build(settings: CompactionSettings | None = None) -> tuple[CompactionStage, ...]:
41
57
  return (
42
- ToolResultBudgetStage(),
43
- SnipCompactStage(),
44
- MicrocompactStage(),
45
- ContextCollapseStage(),
46
- AutocompactStage(),
58
+ ToolResultBudgetStage(settings),
59
+ SnipCompactStage(settings),
60
+ MicrocompactStage(settings),
61
+ ContextCollapseStage(settings),
62
+ AutocompactStage(settings),
47
63
  )
48
64
 
49
65
 
@@ -65,7 +81,7 @@ class ContextPipeline:
65
81
  settings: CompactionSettings | None = None,
66
82
  ) -> ContextPipeline:
67
83
  """使用 `DefaultCompactionStages` 构建管道。"""
68
- return cls(DefaultCompactionStages.build(), settings=settings)
84
+ return cls(DefaultCompactionStages.build(settings), settings=settings)
69
85
 
70
86
  @property
71
87
  def stages(self) -> tuple[CompactionStage, ...]:
@@ -117,6 +133,7 @@ class ContextPipeline:
117
133
  "mode": ctx.mode,
118
134
  }
119
135
 
136
+ _apply_autocompact_failure_patch(state, stages_meta, patch)
120
137
  if not patch:
121
138
  return {}
122
139
  return patch
@@ -198,6 +215,7 @@ class ContextPipeline:
198
215
  "mode": ctx.mode,
199
216
  }
200
217
 
218
+ _apply_autocompact_failure_patch(state, stages_meta, patch)
201
219
  if not patch:
202
220
  return {}
203
221
  return patch
@@ -32,6 +32,8 @@ class CompactionSettings:
32
32
  prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"Skill"}))
33
33
  default_max_context_tokens: int = 120_000
34
34
  default_compress_trigger_fraction: float = 0.8
35
+ # 非 None 时 AutocompactStage 用绝对触发线(由 AgentLoopConfig compact_buffer 派生)。
36
+ autocompact_trigger_token_threshold: int | None = None
35
37
  default_compress_keep_recent_messages: int = 8
36
38
  # --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
37
39
  # 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
@@ -62,6 +64,8 @@ class CompactionSettings:
62
64
  default_factory=QuerySourcePolicy.autocompact_skip_sources
63
65
  )
64
66
  autocompact_use_llm_when_available: bool = True
67
+ is_auto_compact_enabled: bool = True
68
+ max_consecutive_autocompact_failures: int = 3
65
69
  autocompact_llm_excerpt_chars_per_message: int = 20_000
66
70
  # --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
67
71
  # True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
@@ -89,6 +93,12 @@ class CompactionSettings:
89
93
  active_compaction_settings: CompactionSettings = CompactionSettings()
90
94
 
91
95
 
96
+ def autocompact_trigger_threshold_tokens(settings: CompactionSettings) -> int:
97
+ if settings.autocompact_trigger_token_threshold is not None:
98
+ return settings.autocompact_trigger_token_threshold
99
+ return int(settings.default_max_context_tokens * settings.default_compress_trigger_fraction)
100
+
101
+
92
102
  def get_active_compaction_settings() -> CompactionSettings:
93
103
  """返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
94
104
  return active_compaction_settings
@@ -104,6 +114,7 @@ __all__ = [
104
114
  "AutocompactLlmPromptKind",
105
115
  "CompactionSettings",
106
116
  "active_compaction_settings",
117
+ "autocompact_trigger_threshold_tokens",
107
118
  "get_active_compaction_settings",
108
119
  "set_active_compaction_settings",
109
120
  ]
@@ -25,7 +25,11 @@ from langchain_core.messages import HumanMessage, SystemMessage
25
25
  from ..blocking_guard import CompactionBlockingGuard
26
26
  from ..message_utils import stringify_content, total_estimated_tokens_for_messages
27
27
  from ...prompt.compact import build_autocompact_llm_instruction_prompt, get_compact_user_summary_message
28
- from ..settings import CompactionSettings, get_active_compaction_settings
28
+ from ..settings import (
29
+ CompactionSettings,
30
+ autocompact_trigger_threshold_tokens,
31
+ get_active_compaction_settings,
32
+ )
29
33
  from ..types import CompactProgress, ContextCompactionContext, StageResult
30
34
  from .base import CompactionStage
31
35
 
@@ -45,10 +49,25 @@ class AutocompactStage(CompactionStage):
45
49
 
46
50
  def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
47
51
  s = self._effective_settings()
52
+ if not s.is_auto_compact_enabled:
53
+ return StageResult(
54
+ messages=messages,
55
+ tokens_freed=0,
56
+ extra={"autocompact_skipped": "auto_compact_disabled"},
57
+ )
58
+ if ctx.consecutive_autocompact_failures >= s.max_consecutive_autocompact_failures:
59
+ return StageResult(
60
+ messages=messages,
61
+ tokens_freed=0,
62
+ extra={
63
+ "autocompact_skipped": "consecutive_failures",
64
+ "consecutive_autocompact_failures": ctx.consecutive_autocompact_failures,
65
+ },
66
+ )
48
67
  if ctx.query_source in s.autocompact_skip_query_sources:
49
68
  return StageResult(messages=messages, tokens_freed=0, extra={"autocompact_skipped": "query_source"})
50
69
  total = total_estimated_tokens_for_messages(messages, s.num_chars_per_token)
51
- threshold = int(s.default_max_context_tokens * s.default_compress_trigger_fraction)
70
+ threshold = autocompact_trigger_threshold_tokens(s)
52
71
  slack = ctx.snip_tokens_freed_carry if s.autocompact_snip_carry_threshold_slack else 0
53
72
  if total <= threshold + slack:
54
73
  return StageResult(messages=messages, tokens_freed=0)
@@ -66,11 +85,12 @@ class AutocompactStage(CompactionStage):
66
85
  tail = rest[-k:]
67
86
 
68
87
  block_llm = CompactionBlockingGuard(settings=s).should_block_compact_llm(messages)
69
- if (
88
+ attempted_llm = (
70
89
  s.autocompact_use_llm_when_available
71
90
  and ctx.model is not None
72
91
  and not block_llm
73
- ):
92
+ )
93
+ if attempted_llm:
74
94
  summary = _invoke_summary(ctx.model, s, head)
75
95
  if summary:
76
96
  # 修复后:不包含 leading,state 不含 SystemMessage
@@ -104,20 +124,38 @@ class AutocompactStage(CompactionStage):
104
124
  ] + tail
105
125
  new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
106
126
  mode = "prune_blocked_llm" if block_llm else "prune"
127
+ extra: dict[str, Any] = {"autocompact_mode": mode, "folded_messages": len(head)}
128
+ if attempted_llm:
129
+ extra["autocompact_llm_failed"] = True
107
130
  return StageResult(
108
131
  messages=folded,
109
132
  tokens_freed=max(0, total - new_total),
110
- extra={"autocompact_mode": mode, "folded_messages": len(head)},
133
+ extra=extra,
111
134
  )
112
135
 
113
136
  async def arun(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
114
137
  """Async 版本:LLM 摘要使用 astream 流式生成,支持进度回调。"""
115
138
  s = self._effective_settings()
139
+ if not s.is_auto_compact_enabled:
140
+ return StageResult(
141
+ messages=messages,
142
+ tokens_freed=0,
143
+ extra={"autocompact_skipped": "auto_compact_disabled"},
144
+ )
145
+ if ctx.consecutive_autocompact_failures >= s.max_consecutive_autocompact_failures:
146
+ return StageResult(
147
+ messages=messages,
148
+ tokens_freed=0,
149
+ extra={
150
+ "autocompact_skipped": "consecutive_failures",
151
+ "consecutive_autocompact_failures": ctx.consecutive_autocompact_failures,
152
+ },
153
+ )
116
154
  if ctx.query_source in s.autocompact_skip_query_sources:
117
155
  return StageResult(messages=messages, tokens_freed=0, extra={"autocompact_skipped": "query_source"})
118
156
 
119
157
  total = total_estimated_tokens_for_messages(messages, s.num_chars_per_token)
120
- threshold = int(s.default_max_context_tokens * s.default_compress_trigger_fraction)
158
+ threshold = autocompact_trigger_threshold_tokens(s)
121
159
  slack = ctx.snip_tokens_freed_carry if s.autocompact_snip_carry_threshold_slack else 0
122
160
  if total <= threshold + slack:
123
161
  return StageResult(messages=messages, tokens_freed=0)
@@ -135,11 +173,12 @@ class AutocompactStage(CompactionStage):
135
173
  tail = rest[-k:]
136
174
 
137
175
  block_llm = CompactionBlockingGuard(settings=s).should_block_compact_llm(messages)
138
- if (
176
+ attempted_llm = (
139
177
  s.autocompact_use_llm_when_available
140
178
  and ctx.model is not None
141
179
  and not block_llm
142
- ):
180
+ )
181
+ if attempted_llm:
143
182
  summary = await _astream_summary(ctx.model, s, head, ctx.progress_callback)
144
183
  if summary:
145
184
  folded = [
@@ -172,10 +211,13 @@ class AutocompactStage(CompactionStage):
172
211
  ] + tail
173
212
  new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
174
213
  mode = "prune_blocked_llm" if block_llm else "prune"
214
+ extra: dict[str, Any] = {"autocompact_mode": mode, "folded_messages": len(head)}
215
+ if attempted_llm:
216
+ extra["autocompact_llm_failed"] = True
175
217
  return StageResult(
176
218
  messages=folded,
177
219
  tokens_freed=max(0, total - new_total),
178
- extra={"autocompact_mode": mode, "folded_messages": len(head)},
220
+ extra=extra,
179
221
  )
180
222
 
181
223
 
@@ -38,6 +38,7 @@ class ContextCompactionContext:
38
38
  model: Any | None = None
39
39
  snip_tokens_freed_carry: int = 0
40
40
  query_source: str | None = None
41
+ consecutive_autocompact_failures: int = 0
41
42
  config: CompactionConfig = field(default_factory=CompactionConfig)
42
43
  progress_callback: CompactProgressCallback = None
43
44
  mode: str = "proactive"
@@ -47,11 +48,14 @@ class ContextCompactionContext:
47
48
  def from_state(
48
49
  cls, state: Mapping[str, Any], model: Any | None, mode: str = "proactive"
49
50
  ) -> ContextCompactionContext:
51
+ raw_failures = state.get("consecutive_autocompact_failures")
52
+ failures = int(raw_failures) if isinstance(raw_failures, int) else 0
50
53
  return cls(
51
54
  model=model,
52
55
  query_source=state.get("query_source")
53
56
  if isinstance(state.get("query_source"), str)
54
57
  else None,
58
+ consecutive_autocompact_failures=max(0, failures),
55
59
  mode=mode,
56
60
  )
57
61
 
@@ -204,6 +204,7 @@ def create_initial_state(
204
204
  # 恢复路径守卫状态
205
205
  "collapse_retry_available": False,
206
206
  "has_attempted_reactive_compact": False,
207
+ "consecutive_autocompact_failures": 0,
207
208
  "compaction_pipeline_meta": None,
208
209
  "max_output_tokens_override": None,
209
210
  "max_output_tokens_recovery_count": 0,
@@ -248,6 +249,7 @@ class AgentLoopState(TypedDict):
248
249
  withhold_retry_count: int # withhold 已重试次数
249
250
  collapse_retry_available: bool # collapse_drain_retry 相关守卫
250
251
  has_attempted_reactive_compact: bool # reactive_compact 重试守卫
252
+ consecutive_autocompact_failures: int # proactive autocompact LLM 连续失败(CC 熔断)
251
253
  compaction_pipeline_meta: dict[str, Any] | None # prune_and_compress 观测
252
254
  max_output_tokens_override: int | None # max_output_tokens 升档参数
253
255
  max_output_tokens_recovery_count: int # max_output_tokens 恢复计数
@@ -10,20 +10,15 @@ LangChain AgentX Agent Factory
10
10
 
11
11
  参考设计文档:.claude/design/create-opencode-style-agent-design.md
12
12
 
13
- LangChain AgentX Agent 状态图(文字版,含 loop_controller):
13
+ LangChain AgentX Agent 状态图(文字版,含 loop_controller · proactive compact gate):
14
14
 
15
- START
15
+ START → hook_loop_start → prune_and_compress → hook_before_model → model
16
16
  ↓
17
- hook_loop_start
18
- ↓
19
- model 节点(同步/异步,带 OpenCode 退出判断)
20
- ↓
21
- hook_stop
22
- ↓
23
- loop_controller(集中处理 resolution / terminal / 工具调用)
24
- ├─ resolution.kind=terminal → END(或 task reconcile / recovery 分支)
25
- ├─ 有工具调用 → tools → (task_event_*/prune_and_compress) → model
26
- └─ 无工具调用 → END
17
+ hook_stop → loop_controller
18
+ ├─ terminal → hook_loop_end → END
19
+ ├─ tools → (task_event_* →) prune_and_compress → hook_before_model → model
20
+ ├─ 再进 model(无 tools)→ prune_and_compress → hook_before_model → model
21
+ └─ reactive_compact → prune_and_compress(或 task 链)→ … → model
27
22
  """
28
23
 
29
24
  from __future__ import annotations
@@ -39,6 +34,16 @@ from langchain_core.messages import HumanMessage, SystemMessage
39
34
  from langchain_core.runnables import RunnableConfig
40
35
  from langgraph._internal._runnable import RunnableCallable
41
36
  from langgraph.constants import END, START
37
+
38
+ _PRE_MODEL_COMPACT_NODE = "prune_and_compress"
39
+ _BEFORE_MODEL_NODE = "hook_before_model"
40
+
41
+
42
+ def _pre_model_gate_destination(loop_entry_node: str) -> str:
43
+ """CC 对齐:直连 hook_before_model 的 loop 续跑须先过 proactive compact gate。"""
44
+ if loop_entry_node == _BEFORE_MODEL_NODE:
45
+ return _PRE_MODEL_COMPACT_NODE
46
+ return loop_entry_node
42
47
  from langgraph.graph.state import StateGraph
43
48
  import asyncio
44
49
  import dataclasses
@@ -66,8 +71,9 @@ from ..model.schema_and_format import (
66
71
  )
67
72
  from ..model.model_node import ModelNode
68
73
  from ..model.model_nodes import make_model_nodes
69
- from ..context import default_loop_context_compaction
74
+ from ..context import LoopContextCompaction, default_loop_context_compaction
70
75
  from ..context.settings import CompactionSettings
76
+ from ..context.types import CompactProgress
71
77
  from ..injection.dedup import filter_attachment_dicts_by_task_command_id
72
78
  from ..message_contract import to_record
73
79
  from ..model.tool_call_degradation_corrector import (
@@ -102,6 +108,10 @@ from ...memory.memdir.paths import is_memory_enabled
102
108
  from ..prompt import SystemPromptSection, build_effective_system_prompt
103
109
  from ..prompt.session_context import get_default_session_context_sections
104
110
  from ..config import ModelContextResolver
111
+ from ..config.compaction_config import (
112
+ compute_autocompact_trigger_token_threshold,
113
+ resolve_is_auto_compact_enabled,
114
+ )
105
115
  from ..config.interaction_profile import (
106
116
  assemble_services,
107
117
  normalize_config,
@@ -556,7 +566,7 @@ class LoopGraphBuilder:
556
566
  # 运行时需要的额外值存储在 self._* 字段中
557
567
 
558
568
  self._compaction_settings = self._build_compaction_settings()
559
- self._loop_context_compaction = default_loop_context_compaction().with_default_pipeline(
569
+ self._loop_context_compaction = LoopContextCompaction.with_default_pipeline(
560
570
  settings=self._compaction_settings
561
571
  )
562
572
  self._model = model
@@ -585,7 +595,10 @@ class LoopGraphBuilder:
585
595
  session_store.hooks_snapshot = self._hook_snapshot
586
596
  from langchain_agentx.tools.skill.repository_factory import build_skill_repository
587
597
 
588
- session_store.skill_repository = build_skill_repository(self._workspace_cfg)
598
+ session_store.skill_repository = build_skill_repository(
599
+ self._workspace_cfg,
600
+ discovery=self._skill_discovery_layout,
601
+ )
589
602
  self._trace_callback_handler: TraceCallbackHandler | None = None
590
603
  self._trace_tool_callback_handler: TraceCallbackHandler | None = None
591
604
  hook_event_emitter = None
@@ -745,6 +758,7 @@ class LoopGraphBuilder:
745
758
  workspace_root=self._workspace_root,
746
759
  agent_home_segment=self._agent_home_segment,
747
760
  session_cwd=self._workspace_cfg.session_cwd or self._workspace_cfg.workspace_root,
761
+ skill_discovery=self._skill_discovery_layout,
748
762
  )
749
763
  # 用户已在 loader 上注册自定义 AgentRuntimeTool 时,跳过默认 Agent 注册,
750
764
  # 由后续合并循环把用户实例搬入 loop_loader.registry。
@@ -1080,6 +1094,7 @@ class LoopGraphBuilder:
1080
1094
  self._workspace_cfg = workspace_cfg
1081
1095
  self._workspace_root = str(workspace_cfg.workspace_root)
1082
1096
  self._agent_home_segment = workspace_cfg.agent_home_segment
1097
+ self._skill_discovery_layout = self._resolve_skill_discovery_layout()
1083
1098
  from langchain_agentx.workspace import WorkspaceBoundary
1084
1099
 
1085
1100
  self._workspace_boundary = WorkspaceBoundary.build(
@@ -1088,6 +1103,27 @@ class LoopGraphBuilder:
1088
1103
  write_roots=list(self._config.workspace.write_roots) or None,
1089
1104
  )
1090
1105
 
1106
+ def _resolve_skill_discovery_layout(self) -> Any | None:
1107
+ from langchain_agentx.tools.skill.source_paths import SkillDiscoveryLayout
1108
+
1109
+ sd = self._config.workspace.skill_discovery
1110
+ if sd is None:
1111
+ return None
1112
+ return SkillDiscoveryLayout(
1113
+ project_skills_dir=(
1114
+ Path(sd.project_skills_dir).expanduser()
1115
+ if sd.project_skills_dir is not None
1116
+ else None
1117
+ ),
1118
+ global_skills_dir=(
1119
+ Path(sd.global_skills_dir).expanduser()
1120
+ if sd.global_skills_dir is not None
1121
+ else None
1122
+ ),
1123
+ include_managed=sd.include_managed,
1124
+ include_user_home=sd.include_user_home,
1125
+ )
1126
+
1091
1127
  def _sync_tool_policy_roots_from_workspace(self) -> None:
1092
1128
  """挂载 A:compile 后以 AgentLoopConfig.workspace 刷新 loader policy roots。"""
1093
1129
  loader = self._services.loader
@@ -1328,15 +1364,29 @@ class LoopGraphBuilder:
1328
1364
 
1329
1365
  def _build_compaction_settings(self) -> CompactionSettings:
1330
1366
  base = CompactionSettings()
1367
+ context_window = self._model_context_resolver.resolve_context_window(self._config)
1368
+ trigger_threshold = compute_autocompact_trigger_token_threshold(
1369
+ context_window_tokens=context_window,
1370
+ max_output_tokens_reserved=self._config.max_output_tokens_reserved,
1371
+ compact_buffer_tokens=self._config.compact_buffer_tokens,
1372
+ )
1373
+ auto_compact_enabled = resolve_is_auto_compact_enabled(
1374
+ config_is_auto_compact=self._config.is_auto_compact
1375
+ )
1376
+ # G1-A (CC 0331): auto + reactive 路径下不在 call 前 synthetic blocking_limit。
1377
+ enable_main_token_block = base.enable_main_model_context_token_block
1378
+ if auto_compact_enabled:
1379
+ enable_main_token_block = False
1331
1380
  return CompactionSettings(
1332
1381
  num_chars_per_token=base.num_chars_per_token,
1333
1382
  prune_protect_tokens=base.prune_protect_tokens,
1334
1383
  prune_minimum_tokens=base.prune_minimum_tokens,
1335
1384
  prune_protected_tools=base.prune_protected_tools,
1336
- default_max_context_tokens=self._model_context_resolver.resolve_context_window(
1337
- self._config
1338
- ),
1385
+ default_max_context_tokens=context_window,
1339
1386
  default_compress_trigger_fraction=base.default_compress_trigger_fraction,
1387
+ autocompact_trigger_token_threshold=trigger_threshold,
1388
+ is_auto_compact_enabled=auto_compact_enabled,
1389
+ max_consecutive_autocompact_failures=base.max_consecutive_autocompact_failures,
1340
1390
  default_compress_keep_recent_messages=base.default_compress_keep_recent_messages,
1341
1391
  autocompact_llm_prompt_kind=base.autocompact_llm_prompt_kind,
1342
1392
  compact_custom_instructions=base.compact_custom_instructions,
@@ -1356,7 +1406,7 @@ class LoopGraphBuilder:
1356
1406
  compaction_token_warn_fraction=base.compaction_token_warn_fraction,
1357
1407
  compaction_block_compact_llm_fraction=base.compaction_block_compact_llm_fraction,
1358
1408
  compaction_block_main_model_fraction=base.compaction_block_main_model_fraction,
1359
- enable_main_model_context_token_block=base.enable_main_model_context_token_block,
1409
+ enable_main_model_context_token_block=enable_main_token_block,
1360
1410
  enable_tool_transcript_repair_before_llm=base.enable_tool_transcript_repair_before_llm,
1361
1411
  emit_compaction_pipeline_meta=base.emit_compaction_pipeline_meta,
1362
1412
  )
@@ -1400,19 +1450,21 @@ class LoopGraphBuilder:
1400
1450
  context_schema=self._context_schema,
1401
1451
  )
1402
1452
  graph_inner.add_node("model", model_node)
1453
+ graph_inner.add_node(
1454
+ _PRE_MODEL_COMPACT_NODE,
1455
+ RunnableCallable(
1456
+ self._prune_and_compress_node,
1457
+ self._prune_and_compress_node_async,
1458
+ trace=False,
1459
+ ),
1460
+ )
1403
1461
  if tool_node is not None:
1404
1462
  graph_inner.add_node("tools", tool_node)
1405
-
1406
- graph_inner.add_node(
1407
- "prune_and_compress",
1408
- RunnableCallable(
1409
- self._prune_and_compress_node,
1410
- self._prune_and_compress_node_async,
1411
- trace=False,
1412
- ),
1413
- )
1414
1463
  return graph_inner
1415
1464
 
1465
+ def _wire_prune_and_compress_to_before_model(self, graph_inner: Any) -> None:
1466
+ graph_inner.add_edge(_PRE_MODEL_COMPACT_NODE, _BEFORE_MODEL_NODE)
1467
+
1416
1468
  def _prune_and_compress_node(self, state: dict[str, Any], runtime: Any) -> dict[str, Any]:
1417
1469
  return self._run_prune_and_compress_with_trace(state)
1418
1470
 
@@ -1435,6 +1487,31 @@ class LoopGraphBuilder:
1435
1487
  "skipping wait_for_extraction (use ainvoke for CC-aligned wait)"
1436
1488
  )
1437
1489
 
1490
+ @staticmethod
1491
+ def _compact_progress_phase(
1492
+ progress: CompactProgress,
1493
+ *,
1494
+ seen: bool,
1495
+ ) -> tuple[str, bool]:
1496
+ if not seen:
1497
+ return "start", True
1498
+ if progress.stage == "done" and progress.percent >= 100:
1499
+ return "end", True
1500
+ return "tick", True
1501
+
1502
+ def _merge_compact_progress_into_patch(
1503
+ self,
1504
+ patch: dict[str, Any],
1505
+ progress_events: list[dict[str, Any]],
1506
+ ) -> None:
1507
+ if not progress_events:
1508
+ return
1509
+ meta = patch.get("compaction_pipeline_meta")
1510
+ if not isinstance(meta, dict):
1511
+ meta = {}
1512
+ patch["compaction_pipeline_meta"] = meta
1513
+ meta["compact_progress"] = progress_events
1514
+
1438
1515
  def _apply_prune_and_compress(self, state: dict[str, Any]) -> dict[str, Any]:
1439
1516
  if self._session_memory_compact_bridge is not None:
1440
1517
  compacted_messages = self._session_memory_compact_bridge.try_compact(
@@ -1455,6 +1532,55 @@ class LoopGraphBuilder:
1455
1532
  patch["collapse_retry_available"] = bool(compaction_meta.get("tokens_freed_sum", 0))
1456
1533
  return patch
1457
1534
 
1535
+ async def _apply_prune_and_compress_async(self, state: dict[str, Any]) -> dict[str, Any]:
1536
+ if self._session_memory_compact_bridge is not None:
1537
+ compacted_messages = self._session_memory_compact_bridge.try_compact(
1538
+ list(state.get("messages") or [])
1539
+ )
1540
+ if compacted_messages is not None:
1541
+ return {
1542
+ "messages": compacted_messages,
1543
+ "collapse_retry_available": True,
1544
+ "compaction_pipeline_meta": {
1545
+ "autocompact_mode": "session_memory",
1546
+ "session_memory_compaction": "applied",
1547
+ },
1548
+ }
1549
+ progress_events: list[dict[str, Any]] = []
1550
+ seen_progress = False
1551
+
1552
+ def _on_progress(progress: CompactProgress) -> None:
1553
+ nonlocal seen_progress
1554
+ phase, seen_progress = self._compact_progress_phase(
1555
+ progress, seen=seen_progress
1556
+ )
1557
+ progress_events.append(
1558
+ {
1559
+ "phase": phase,
1560
+ "stage": progress.stage,
1561
+ "stage_index": progress.stage_index,
1562
+ "total_stages": progress.total_stages,
1563
+ "percent": progress.percent,
1564
+ "detail": progress.detail,
1565
+ }
1566
+ )
1567
+
1568
+ emit_progress = bool(
1569
+ self._compaction_settings.is_auto_compact_enabled
1570
+ and self._compaction_settings.emit_compaction_pipeline_meta
1571
+ )
1572
+ patch = await self._loop_context_compaction.arun(
1573
+ state,
1574
+ model=self._model,
1575
+ progress_callback=_on_progress if emit_progress else None,
1576
+ )
1577
+ if emit_progress:
1578
+ self._merge_compact_progress_into_patch(patch, progress_events)
1579
+ compaction_meta = patch.get("compaction_pipeline_meta")
1580
+ if isinstance(compaction_meta, dict):
1581
+ patch["collapse_retry_available"] = bool(compaction_meta.get("tokens_freed_sum", 0))
1582
+ return patch
1583
+
1458
1584
  def _run_prune_and_compress_with_trace(self, state: dict[str, Any]) -> dict[str, Any]:
1459
1585
  """执行 compaction,并在当前 session 内记录 compaction span(sync 路径)。"""
1460
1586
  collector = state.get("_trace_collector")
@@ -1520,7 +1646,7 @@ class LoopGraphBuilder:
1520
1646
  try:
1521
1647
  if self._session_memory_compact_bridge is not None:
1522
1648
  await self._session_memory_compact_bridge.wait_for_extraction()
1523
- patch = self._apply_prune_and_compress(state)
1649
+ patch = await self._apply_prune_and_compress_async(state)
1524
1650
  except Exception as exc:
1525
1651
  if span_id is not None:
1526
1652
  collector.span_end(
@@ -1567,6 +1693,7 @@ class LoopGraphBuilder:
1567
1693
  skill_prefetch_provider=skill_prefetch_provider,
1568
1694
  entry_node=entry_node,
1569
1695
  )
1696
+ self._wire_prune_and_compress_to_before_model(graph_inner)
1570
1697
  graph_inner.add_edge("hook_before_model", "model")
1571
1698
  return {
1572
1699
  "entry_node": entry_node,
@@ -1870,12 +1997,8 @@ class LoopGraphBuilder:
1870
1997
  RunnableCallable(_task_event_reserve_node, None, trace=False),
1871
1998
  )
1872
1999
  graph_inner.add_edge("task_event_commit_ack", "task_event_reserve")
1873
- # Phase 5A:压缩收束到「每轮进入 model 之前」唯一入口;有 task 链时在注入附件之后再压缩。
1874
- if tool_node is not None:
1875
- graph_inner.add_edge("task_event_reserve", "prune_and_compress")
1876
- graph_inner.add_edge("prune_and_compress", raw_loop_entry_node)
1877
- else:
1878
- graph_inner.add_edge("task_event_reserve", raw_loop_entry_node)
2000
+ # Phase 5A / proactive autocompact:task 注入后再压缩,再进 hook_before_model。
2001
+ graph_inner.add_edge("task_event_reserve", _PRE_MODEL_COMPACT_NODE)
1879
2002
  return "task_event_commit_ack"
1880
2003
 
1881
2004
  def _find_skill_prefetch_provider(
@@ -1958,10 +2081,10 @@ class LoopGraphBuilder:
1958
2081
  )
1959
2082
  graph_inner.add_edge(START, "skill_init")
1960
2083
  graph_inner.add_edge("skill_init", "hook_loop_start")
1961
- graph_inner.add_edge("hook_loop_start", entry_node)
2084
+ graph_inner.add_edge("hook_loop_start", _PRE_MODEL_COMPACT_NODE)
1962
2085
  return
1963
2086
  graph_inner.add_edge(START, "hook_loop_start")
1964
- graph_inner.add_edge("hook_loop_start", entry_node)
2087
+ graph_inner.add_edge("hook_loop_start", _PRE_MODEL_COMPACT_NODE)
1965
2088
 
1966
2089
  def _wire_main_edges(self, *, graph_inner: Any, tool_node: Any, routing: dict[str, Any]) -> None:
1967
2090
  loop_entry_node = routing["loop_entry_node"]
@@ -2014,8 +2137,8 @@ class LoopGraphBuilder:
2014
2137
  tools_model_destination = loop_entry_node
2015
2138
  tools_to_model_destinations = [loop_entry_node, exit_node]
2016
2139
  else:
2017
- tools_model_destination = "prune_and_compress"
2018
- tools_to_model_destinations = ["prune_and_compress", exit_node]
2140
+ tools_model_destination = _PRE_MODEL_COMPACT_NODE
2141
+ tools_to_model_destinations = [_PRE_MODEL_COMPACT_NODE, exit_node]
2019
2142
 
2020
2143
  graph_inner.add_conditional_edges(
2021
2144
  "tools",
@@ -2030,8 +2153,6 @@ class LoopGraphBuilder:
2030
2153
  ),
2031
2154
  tools_to_model_destinations,
2032
2155
  )
2033
- if not wire_task_event_nodes:
2034
- graph_inner.add_edge("prune_and_compress", loop_entry_node)
2035
2156
  graph_inner.add_edge(loop_exit_node, "hook_after_model")
2036
2157
  graph_inner.add_edge("hook_after_model", "hook_stop")
2037
2158
  self._wire_loop_controller(
@@ -2053,11 +2174,12 @@ class LoopGraphBuilder:
2053
2174
  wire_task_event_nodes: bool,
2054
2175
  tools_destination: str | None = None,
2055
2176
  ) -> None:
2177
+ model_gate = _pre_model_gate_destination(loop_entry_node)
2056
2178
  graph_inner.add_node(
2057
2179
  "loop_controller",
2058
2180
  RunnableCallable(
2059
2181
  _make_loop_controller_node(
2060
- model_destination=loop_entry_node,
2182
+ model_destination=model_gate,
2061
2183
  tools_destination=tools_destination or loop_entry_node,
2062
2184
  structured_output_tools=self._structured_output_tools,
2063
2185
  end_destination=exit_node,
@@ -2081,7 +2203,7 @@ class LoopGraphBuilder:
2081
2203
  def _reactive_compact_node(state, runtime):
2082
2204
  node = ReactiveCompactNode(
2083
2205
  compaction_service=default_loop_context_compaction(),
2084
- loop_entry_destination=loop_entry_node,
2206
+ loop_entry_destination=model_gate,
2085
2207
  end_destination=exit_node,
2086
2208
  )
2087
2209
  return node(state)
@@ -2090,13 +2212,14 @@ class LoopGraphBuilder:
2090
2212
  "reactive_compact",
2091
2213
  RunnableCallable(_reactive_compact_node, None, trace=False),
2092
2214
  )
2215
+ reactive_destinations = [model_gate, exit_node]
2093
2216
  graph_inner.add_conditional_edges(
2094
2217
  "reactive_compact",
2095
- lambda state: state.get("destination", loop_entry_node),
2096
- [loop_entry_node, exit_node],
2218
+ lambda state: state.get("destination", model_gate),
2219
+ reactive_destinations,
2097
2220
  )
2098
2221
 
2099
- loop_controller_destinations = [loop_entry_node, exit_node, "reactive_compact"]
2222
+ loop_controller_destinations = [model_gate, exit_node, "reactive_compact"]
2100
2223
  if tools_destination is not None:
2101
2224
  loop_controller_destinations.insert(0, tools_destination)
2102
2225
  if wire_task_event_nodes and "task_event_commit_ack" not in loop_controller_destinations:
@@ -2118,16 +2241,17 @@ class LoopGraphBuilder:
2118
2241
  wire_task_event_nodes: bool,
2119
2242
  ) -> None:
2120
2243
  graph_inner.add_edge(loop_exit_node, "hook_after_model")
2244
+ model_gate = _pre_model_gate_destination(loop_entry_node)
2121
2245
  graph_inner.add_conditional_edges(
2122
2246
  "hook_after_model",
2123
2247
  RunnableCallable(
2124
2248
  _make_model_to_model_edge(
2125
- model_destination=loop_entry_node,
2249
+ model_destination=model_gate,
2126
2250
  end_destination="hook_stop",
2127
2251
  ),
2128
2252
  trace=False,
2129
2253
  ),
2130
- [loop_entry_node, "hook_stop"],
2254
+ [model_gate, "hook_stop"],
2131
2255
  )
2132
2256
  self._wire_loop_controller(
2133
2257
  graph_inner=graph_inner,
@@ -128,6 +128,7 @@ class LangchainAgentEventType(Enum):
128
128
  HOOK_PROGRESS = "hook-progress"
129
129
  HOOK_FINISHED = "hook-finished"
130
130
  COMPACT_BOUNDARY = "compact-boundary"
131
+ COMPACT_PROGRESS = "compact-progress"
131
132
  MEMORY_SAVED = "memory-saved"
132
133
  SUBAGENT_START = "subagent-start"
133
134
  SUBAGENT_END = "subagent-end"
@@ -850,6 +851,16 @@ class AgentEventProjector:
850
851
 
851
852
  if self._is_compact_node(name):
852
853
  meta = extract_compaction_meta_from_chain_end_data(data)
854
+ if isinstance(meta, dict) and self.enable_compact_events:
855
+ raw_progress = meta.get("compact_progress")
856
+ if isinstance(raw_progress, list):
857
+ for item in raw_progress:
858
+ if isinstance(item, dict):
859
+ yield self._event(
860
+ event_type=LangchainAgentEventType.COMPACT_PROGRESS,
861
+ data=dict(item),
862
+ step_index=self.state.step_index or None,
863
+ )
853
864
  if is_user_visible_compact(meta):
854
865
  if self.enable_compact_events:
855
866
  compact_data = self._extract_compact_summary(data)
@@ -131,6 +131,7 @@ class ToolRuntimeLoader:
131
131
  workspace_root: str,
132
132
  agent_home_segment: str = ".langchain_agentx",
133
133
  session_cwd: str | Path | None = None,
134
+ skill_discovery: Any | None = None,
134
135
  ) -> LoopLoader:
135
136
  """构造独立的 per-loop 工具注册表(不污染 loader 主 registry)。"""
136
137
  return LoopLoader(
@@ -138,6 +139,7 @@ class ToolRuntimeLoader:
138
139
  workspace_root=workspace_root,
139
140
  agent_home_segment=agent_home_segment,
140
141
  session_cwd=session_cwd,
142
+ skill_discovery=skill_discovery,
141
143
  )
142
144
 
143
145
  def create_session(self, session_id: str | None = None) -> AgentSessionStore:
@@ -34,11 +34,13 @@ class LoopLoader:
34
34
  workspace_root: str,
35
35
  agent_home_segment: str = ".langchain_agentx",
36
36
  session_cwd: str | Path | None = None,
37
+ skill_discovery: Any | None = None,
37
38
  ) -> None:
38
39
  self._process = process
39
40
  self._workspace_root = workspace_root
40
41
  self._agent_home = agent_home_segment
41
42
  self._session_cwd = session_cwd
43
+ self._skill_discovery = skill_discovery
42
44
  self._registry = RuntimeToolRegistry(
43
45
  pipeline=process.pipeline,
44
46
  adapter=process.adapter,
@@ -107,7 +109,7 @@ class LoopLoader:
107
109
  agent_home_segment=self._agent_home,
108
110
  session_cwd=effective_cwd,
109
111
  )
110
- self._merge_skill_read_roots_policy(cfg)
112
+ self._merge_skill_read_roots_policy(cfg, discovery=self._skill_discovery)
111
113
  self._inject_project_memory_policy(cfg)
112
114
  plugin_ex = PluginCacheGlobExclusions(
113
115
  cache_root=str(cfg.plugin_cache_dir.resolve()),
@@ -211,11 +213,19 @@ class LoopLoader:
211
213
  setter(patterns)
212
214
  return patterns
213
215
 
214
- def _merge_skill_read_roots_policy(self, workspace_cfg: Any) -> None:
216
+ def _merge_skill_read_roots_policy(
217
+ self,
218
+ workspace_cfg: Any,
219
+ *,
220
+ discovery: Any | None = None,
221
+ ) -> None:
215
222
  """G-2:skill 发现目录并入 read_roots(在 project_memory 注入之前)。"""
216
223
  from langchain_agentx.tools.skill.source_paths import resolve_skill_read_roots
217
224
 
218
- roots = resolve_skill_read_roots(workspace_cfg)
225
+ roots = resolve_skill_read_roots(
226
+ workspace_cfg,
227
+ discovery=discovery if discovery is not None else self._skill_discovery,
228
+ )
219
229
  if not roots:
220
230
  return
221
231
  base = self._process.effective_policy_config()
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
35
35
  from langchain_agentx.plugin.registries import SkillRegistry
36
36
  from langchain_agentx.workspace.config import ResolvedAgentStateConfig
37
37
 
38
- SkillLoadedFrom = Literal["bundled", "plugin", "managed", "user", "project", "dynamic"]
38
+ SkillLoadedFrom = Literal["bundled", "plugin", "managed", "user", "project", "global", "dynamic"]
39
39
 
40
40
 
41
41
  @dataclass(frozen=True, slots=True)
@@ -57,6 +57,8 @@ class MultiSourceSkillCommandRepository:
57
57
 
58
58
  for spec in self._sources:
59
59
  skills_dir = spec.skills_dir
60
+ if not skills_dir.expanduser().resolve().is_dir():
61
+ continue
60
62
  if self._gitignore_guard.should_skip_skills_dir(skills_dir, cwd=self._cwd):
61
63
  continue
62
64
  for cmd in self._dir_loader.load_commands_from_dir(skills_dir):
@@ -17,7 +17,7 @@ from langchain_agentx.workspace.config import ResolvedAgentStateConfig
17
17
 
18
18
  from .loader import SkillCommandRepository
19
19
  from .multi_source_repository import MultiSourceSkillCommandRepository
20
- from .source_paths import resolve_skill_source_specs
20
+ from .source_paths import SkillDiscoveryLayout, resolve_skill_source_specs
21
21
 
22
22
  SkillRepository = Union[SkillCommandRepository, MultiSourceSkillCommandRepository]
23
23
 
@@ -26,13 +26,15 @@ def build_skill_repository(
26
26
  workspace_cfg: ResolvedAgentStateConfig,
27
27
  *,
28
28
  skills_root: Path | None = None,
29
+ discovery: SkillDiscoveryLayout | None = None,
29
30
  ) -> SkillRepository:
30
31
  if skills_root is not None:
31
32
  return SkillCommandRepository(Path(skills_root).expanduser().resolve())
33
+ cwd = workspace_cfg.session_cwd or workspace_cfg.workspace_root
32
34
  return MultiSourceSkillCommandRepository(
33
- resolve_skill_source_specs(workspace_cfg),
35
+ resolve_skill_source_specs(workspace_cfg, discovery=discovery),
34
36
  workspace_root=workspace_cfg.workspace_root,
35
- cwd=workspace_cfg.workspace_root,
37
+ cwd=cwd,
36
38
  )
37
39
 
38
40
 
@@ -26,7 +26,21 @@ from langchain_agentx.workspace.project_key import resolve_config_home_dir
26
26
  if TYPE_CHECKING:
27
27
  from .models import SkillCommand
28
28
 
29
- DiskSkillSource = Literal["managed", "user", "project"]
29
+ DiskSkillSource = Literal["managed", "user", "project", "global"]
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class SkillDiscoveryLayout:
34
+ """显式 skill 发现层级(Workflow 节点 / 双 workspace 宿主)。
35
+
36
+ first-wins 顺序:project → global → user → managed(与 CC 项目覆盖全局一致)。
37
+ 未设 ``discovery`` 时保留 legacy:managed → user → project。
38
+ """
39
+
40
+ project_skills_dir: Path | None = None
41
+ global_skills_dir: Path | None = None
42
+ include_managed: bool = False
43
+ include_user_home: bool = False
30
44
 
31
45
 
32
46
  @dataclass(frozen=True, slots=True)
@@ -61,23 +75,35 @@ def resolve_managed_skills_dir(agent_home_segment: str) -> Path:
61
75
  return resolve_managed_settings_root() / segment / "skills"
62
76
 
63
77
 
78
+ def _existing_skills_dir(path: Path) -> Path | None:
79
+ try:
80
+ resolved = path.expanduser().resolve()
81
+ except OSError:
82
+ return None
83
+ if resolved.is_dir():
84
+ return resolved
85
+ return None
86
+
87
+
64
88
  def resolve_skill_read_roots(
65
89
  workspace_cfg: ResolvedAgentStateConfig,
66
90
  *,
67
91
  project_skills_dir: Path | None = None,
92
+ discovery: SkillDiscoveryLayout | None = None,
68
93
  ) -> tuple[Path, ...]:
69
- """Skill 发现目录绝对路径(与 ``resolve_skill_source_specs`` 同源,去重保序)。"""
94
+ """Skill 可读根(仅 **已存在** 的目录;S-5 发现语义,非 boundary 错误)。"""
70
95
  seen: set[Path] = set()
71
96
  roots: list[Path] = []
72
97
  for spec in resolve_skill_source_specs(
73
98
  workspace_cfg,
74
99
  project_skills_dir=project_skills_dir,
100
+ discovery=discovery,
75
101
  ):
76
- resolved = spec.skills_dir.expanduser().resolve()
77
- if resolved in seen:
102
+ existing = _existing_skills_dir(spec.skills_dir)
103
+ if existing is None or existing in seen:
78
104
  continue
79
- seen.add(resolved)
80
- roots.append(resolved)
105
+ seen.add(existing)
106
+ roots.append(existing)
81
107
  return tuple(roots)
82
108
 
83
109
 
@@ -85,20 +111,44 @@ def resolve_skill_source_specs(
85
111
  workspace_cfg: ResolvedAgentStateConfig,
86
112
  *,
87
113
  project_skills_dir: Path | None = None,
114
+ discovery: SkillDiscoveryLayout | None = None,
88
115
  ) -> list[SkillSourceSpec]:
89
- """managed → user → project(first-wins 由 repository 执行)。"""
116
+ """Skill 源列表;repository first-wins 按列表顺序。"""
90
117
  segment = workspace_cfg.agent_home_segment
91
- project_dir = project_skills_dir or workspace_cfg.skills_dir
92
- specs: list[SkillSourceSpec] = []
93
- if not policy_skills_disabled():
118
+ if discovery is None:
119
+ project_dir = project_skills_dir or workspace_cfg.skills_dir
120
+ specs: list[SkillSourceSpec] = []
121
+ if not policy_skills_disabled():
122
+ specs.append(SkillSourceSpec(resolve_managed_skills_dir(segment), "managed"))
123
+ specs.append(SkillSourceSpec(resolve_user_skills_dir(segment), "user"))
124
+ specs.append(SkillSourceSpec(Path(project_dir).expanduser().resolve(), "project"))
125
+ return specs
126
+
127
+ project_dir = (
128
+ discovery.project_skills_dir
129
+ or project_skills_dir
130
+ or workspace_cfg.skills_dir
131
+ )
132
+ specs = [
133
+ SkillSourceSpec(Path(project_dir).expanduser().resolve(), "project"),
134
+ ]
135
+ if discovery.global_skills_dir is not None:
136
+ specs.append(
137
+ SkillSourceSpec(
138
+ Path(discovery.global_skills_dir).expanduser().resolve(),
139
+ "global",
140
+ )
141
+ )
142
+ if discovery.include_user_home:
143
+ specs.append(SkillSourceSpec(resolve_user_skills_dir(segment), "user"))
144
+ if not policy_skills_disabled() and discovery.include_managed:
94
145
  specs.append(SkillSourceSpec(resolve_managed_skills_dir(segment), "managed"))
95
- specs.append(SkillSourceSpec(resolve_user_skills_dir(segment), "user"))
96
- specs.append(SkillSourceSpec(Path(project_dir).expanduser().resolve(), "project"))
97
146
  return specs
98
147
 
99
148
 
100
149
  __all__ = [
101
150
  "DiskSkillSource",
151
+ "SkillDiscoveryLayout",
102
152
  "SkillSourceSpec",
103
153
  "SourcedSkillEntry",
104
154
  "policy_skills_disabled",
@@ -149,6 +149,10 @@ class ProjectMemoryLayoutResolver:
149
149
  key_sess = self.sanitizer.sanitize(str(cwd))
150
150
  l2_project_dir = config_home / _PROJECTS_DIRNAME / key_l2
151
151
  sess_project_dir = config_home / _PROJECTS_DIRNAME / key_sess
152
+ # SSOT:布局解析即保证 projects/{key} 存在,避免启动链路先读/扫描 transcript 时
153
+ # FileNotFoundError(子目录首次启动、key_sess 全新时必现)。
154
+ l2_project_dir.mkdir(parents=True, exist_ok=True)
155
+ sess_project_dir.mkdir(parents=True, exist_ok=True)
152
156
  override = _resolve_memory_dir_override(memory_dir_override)
153
157
  return ProjectMemoryLayout(
154
158
  agent_home_segment=agent_home_segment.strip(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.3.2
3
+ Version: 2.3.4
4
4
  Summary: LangChain/LangGraph-based agent utilities for CodeBaseX.
5
5
  Author-email: GoodMood2008 <GoodMood2008@users.noreply.github.com>
6
6
  License: Apache License
@@ -1,4 +1,4 @@
1
- langchain_agentx/__init__.py,sha256=UYRKrpqJvIQs_odLFNlRSJVoGCAv6UA1VLWG64SQ0gE,1614
1
+ langchain_agentx/__init__.py,sha256=t5u-2g4slzGHVtYyJTJcFQgv_d5BfqHyJWeSDe9FMHY,1614
2
2
  langchain_agentx/command/__init__.py,sha256=Ej260S5uFQcmEtGZQG_NH7BYjHoStrpBLtGUsBTxyZk,631
3
3
  langchain_agentx/command/allowed_tools.py,sha256=TVA0VM8rm98H-QbLK_GRiX5RhEAZFxKawliMi4kk96A,3359
4
4
  langchain_agentx/command/context.py,sha256=DIGOGPBw5UGDBm0WNNJlKx1h_9rCRmcdROv4DT61Qug,731
@@ -29,10 +29,11 @@ langchain_agentx/loop/benchmark_frozen_config.py,sha256=YI4IaDVMBOEucF36F2oNv5mw
29
29
  langchain_agentx/loop/loop_abort.py,sha256=cKlHxZh0u7-drCVqEiRtzkO270MHw6grJuZ1NpKoGfo,3250
30
30
  langchain_agentx/loop/message_contract.py,sha256=Twl83TneU2PRdxgGRBxs6S9_ovRT6cb5WeCUzd6oJnE,2196
31
31
  langchain_agentx/loop/run_context.py,sha256=dhNiLhvqD_QYacGMA0B5P3wWpSe3O6wU_XcVEzb86Z4,8303
32
- langchain_agentx/loop/config/__init__.py,sha256=WdRQVXuvGU4myaGOZ8guFFvUb66jfsAIp9DLANJGieg,1083
32
+ langchain_agentx/loop/config/__init__.py,sha256=0pf3Mml7LZpQ1C3_AtROFQClrBQegaGCG4YCTfSr9v8,1137
33
33
  langchain_agentx/loop/config/agent_config.py,sha256=tOnAHFor6dkle3FvUOVx0YsmiYVTH11UrWSyTmOLV0c,3100
34
+ langchain_agentx/loop/config/compaction_config.py,sha256=MmXqY47-9nPDhdXvnU92Xp0XeaZ605ErQwX9d2emEkM,1176
34
35
  langchain_agentx/loop/config/interaction_profile.py,sha256=UWrlI3aTWjYRq1mDeXL-xL-iCrQU-bXDXhETP6q9icY,10020
35
- langchain_agentx/loop/config/loop_config.py,sha256=f_McUyP8xJAhBka3bPOkk7dkPCe1ZLRX0HI5gsEb370,15680
36
+ langchain_agentx/loop/config/loop_config.py,sha256=3kYDXg91PSDNVUdHGm6In0mHJRpfrkITs4OLKdOngYY,16180
36
37
  langchain_agentx/loop/config/loop_runtime_overlay.py,sha256=PuB4vFdqCHXRbAFhAJIrtwq9pUIrHafyn0ApD4s06KU,2440
37
38
  langchain_agentx/loop/config/model_context_resolver.py,sha256=o9RR0Vnu07TD4Y3V8jk5V1NfkMcPcLjE8uaaLOe22Qo,4067
38
39
  langchain_agentx/loop/config/model_retry_config.py,sha256=bakCbGeNRO-_SXMT_LLE3nu3m9scZe1MSxPdyEGO99w,8178
@@ -45,13 +46,13 @@ langchain_agentx/loop/context/__init__.py,sha256=RXVTFjHcH6_8q1x13Qk9ZP8RVbqZoyh
45
46
  langchain_agentx/loop/context/blocking_guard.py,sha256=Rmt5qZdE8Zf0bt-Q6WwMSfG869772PE8w1mNPvBQRCQ,3789
46
47
  langchain_agentx/loop/context/compaction_service.py,sha256=MwAPPaT31XbzgwqpuknGBZlYBRKxwyhWXDifEnBgh2E,3725
47
48
  langchain_agentx/loop/context/message_utils.py,sha256=6qIwtHeh63OlMqOnPUpEEWRknU9KJTGkcY1v3tz6feY,2010
48
- langchain_agentx/loop/context/pipeline.py,sha256=Ry90p-lrno2jgoKBLEUJle9evJCFJ_IBSe9sru0XjPM,7486
49
+ langchain_agentx/loop/context/pipeline.py,sha256=PrhCWWKUGGwMGd-F8UX6vt3U7tlZo2ZZDLV0CTTDSJ4,8279
49
50
  langchain_agentx/loop/context/query_source.py,sha256=-wqVpST64jXF1ZelcGdvIsJE4ngv54eco4gmeS3pyyg,5118
50
51
  langchain_agentx/loop/context/recovery_mode.py,sha256=ytHGnE-g91pDQIkyVcfxEw6XgJGZ5eVJ12jb3zaeXWU,1100
51
- langchain_agentx/loop/context/settings.py,sha256=K9WRpvyUEo57gDxt4qpo0ouE8ihN5L0vGy3NmdIbssw,5987
52
- langchain_agentx/loop/context/types.py,sha256=UpnurkJfYsmbZXkrwbaDuVpZYJ4ydPHRNiUijL6fh3s,3382
52
+ langchain_agentx/loop/context/settings.py,sha256=EO35T2P5tIUIqkO42ffLTRD-U-w2aRxeAYuHFHYc0nE,6589
53
+ langchain_agentx/loop/context/types.py,sha256=jWXibGiJY6sQnd5-OQu9z7ZZM8Efi1X-6M8zJHNutqw,3637
53
54
  langchain_agentx/loop/context/stages/__init__.py,sha256=wk9CFh8_Akw8qs3Dldnx14VE1MhyF5i9Cg3phTVChhg,892
54
- langchain_agentx/loop/context/stages/autocompact.py,sha256=C2CW-H5NQbNSN7fCcxJ8o-liAc6uuPTBvoy4PJjdkGk,12014
55
+ langchain_agentx/loop/context/stages/autocompact.py,sha256=7segcCEl0kk9hK6YRHAbM67e8As1sE_CRaUUfUnHFh0,13581
55
56
  langchain_agentx/loop/context/stages/base.py,sha256=LVfCp-DLySrTExy0J-M40tDJvzbOXxfZxXvi7XQayA8,1380
56
57
  langchain_agentx/loop/context/stages/collapse.py,sha256=yOf3nfWZr83rq2qnhUpuTCLFHcGvF7my8klMLmfZ7jc,2971
57
58
  langchain_agentx/loop/context/stages/microcompact.py,sha256=WnT58w85xcaSzsGEww5TEnU1noAzkao3ZgAARo65a7Y,3142
@@ -60,7 +61,7 @@ langchain_agentx/loop/context/stages/snip.py,sha256=AZy3D2XPrL1GQU_gE9isKApcphXw
60
61
  langchain_agentx/loop/context/stages/tool_result_budget.py,sha256=zQcJynRnC8QF30cEDduHkso6jChYjckVcnOjYSrCrtg,3833
61
62
  langchain_agentx/loop/exit/__init__.py,sha256=3-CMz5ApkJeBc549vRfgNABenyYKVbib4IdkErVtGcM,528
62
63
  langchain_agentx/loop/exit/api_error_detection.py,sha256=QzyMTov4VrkpHmn288Tw7DN00hiyJNXMKlOrCbvsiAA,1479
63
- langchain_agentx/loop/exit/exit_logic.py,sha256=nJzerVtTmS1iIY3PyBRet7jZ9v9pi-jUO3mJwzqxcnw,13922
64
+ langchain_agentx/loop/exit/exit_logic.py,sha256=3I63n9pyjHD9CfpboWSLNm2O2J8c5PlVjcxesir40k8,14068
64
65
  langchain_agentx/loop/exit/query_source.py,sha256=b_VB13zxNbLAqUAuDt1E3SII3jNLagY-FdN6SbczTsI,2082
65
66
  langchain_agentx/loop/exit/reason_codes.py,sha256=Nk1x2ipftRIRa4U1Z2goyXXWB9TsPFL9TW6WfvtMCEo,4810
66
67
  langchain_agentx/loop/exit/resolution.py,sha256=taIMHzAUtgrQoU9446-CUXBN0PyCujhl6wBQfbxR1qI,1427
@@ -68,7 +69,7 @@ langchain_agentx/loop/exit/terminal.py,sha256=_DAZk-kS4Xd0nxMlayLo60-uj1Y5ozgBjL
68
69
  langchain_agentx/loop/exit/withheld_error.py,sha256=LfX3j7Rgd9VoMzlz7GOs9TPuRe7RXr_Gxweyotx3tqQ,1846
69
70
  langchain_agentx/loop/graph/__init__.py,sha256=9pUUqn7G87n3lV45iYIU24j0wcTHyCD--SSdtv3urh4,136
70
71
  langchain_agentx/loop/graph/builtin_loop_control.py,sha256=31o4H6SDGC_vECSliXecuVEnzlD1ahlVZ5JzEpxYY04,9248
71
- langchain_agentx/loop/graph/factory.py,sha256=BMNHUGqNhOhcqxtdsB0TeI5ZHqK_0QdDwb3K32LmhxA,104607
72
+ langchain_agentx/loop/graph/factory.py,sha256=n0lszl2jYBq1G_rzgmt0-0mCmTa3zuTqrVCow4lUMso,109909
72
73
  langchain_agentx/loop/graph/graph_edges.py,sha256=kW6Npx-nMIPzcaisCxVPkTwf5Y9GOKy1NWCNkJWeJBQ,47370
73
74
  langchain_agentx/loop/graph/reactive_compact_node.py,sha256=633b5hTnR3BK8GBEKWhzOwGB0BQrVRKozNxDomWFjLo,8762
74
75
  langchain_agentx/loop/graph/runtime_tools_node.py,sha256=wQkbc0ophyefpPZEc0q1vKoJOIdS4S6d6hSsrkdRLDk,7387
@@ -217,7 +218,7 @@ langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8
217
218
  langchain_agentx/observability/events/__init__.py,sha256=Zr9Iguf9XsJQLsCJy13YFdBtXHPEnI9aSWXWmA2MisA,1497
218
219
  langchain_agentx/observability/events/accumulated_text_scope.py,sha256=M4OvuQTgReF8SFrOmr-M5vyOJpzfvxj_DKD5bVpGb70,3169
219
220
  langchain_agentx/observability/events/agent_defaults.py,sha256=gmX0P__lux5xbCb8dtAlVCb3wZ7avaqFleoW4Q5anw4,1187
220
- langchain_agentx/observability/events/agent_event_projector.py,sha256=Rktmy2D3qizCejAChbuOiAa06Tnup2Z6cVNATfrrxkU,63995
221
+ langchain_agentx/observability/events/agent_event_projector.py,sha256=SthsPhWuM__Bfm3GoxHw-JCoMQ5xgn_eDgi5n4ib6-4,64600
221
222
  langchain_agentx/observability/events/api_retry_projection.py,sha256=XGb09sNOi6k1ZGW4YqMLBE8GDxNwNMprk62jV9TS9dQ,2433
222
223
  langchain_agentx/observability/events/compact_event_gate.py,sha256=50RJemwB1lnQn_NFWOFSgU418ZUR8dbd_v0538PT7zM,4751
223
224
  langchain_agentx/observability/events/factory.py,sha256=gqMATIK9tBQzaPWSPhCCSPuCq-WF562ZDTnFUaMm9Qc,1570
@@ -406,8 +407,8 @@ langchain_agentx/tool_runtime/failure_event_emitter.py,sha256=C7c4Gc7Vo_R6_ah2Xx
406
407
  langchain_agentx/tool_runtime/identical_call_cache.py,sha256=v8WWuTHWR5ThujcMO4Ty28mZS_w-JN24267fZ4PHWK8,3865
407
408
  langchain_agentx/tool_runtime/inner_permission_chain.py,sha256=50wYXRu7Hi-rvDiI2QxSLC4uBAsJwG3Vp-v3AutLY4E,17629
408
409
  langchain_agentx/tool_runtime/limits.py,sha256=imE1IetmyKQM82INR0VkP3qZNM4bjUJuf1CG5BkY0uw,356
409
- langchain_agentx/tool_runtime/loader.py,sha256=vT1_1QoBCpDGhe9sWRqgDZBcuErF35rAcOAENDrUcCI,9452
410
- langchain_agentx/tool_runtime/loop_loader.py,sha256=H8QKRmoi3ni-8vkyx90W2zfC4quDOfnNTc3d4ote4hM,9615
410
+ langchain_agentx/tool_runtime/loader.py,sha256=PL9WTWhK4BffoVsIjvCZP45tXbE8a2Zc0vp0bJKg2ew,9541
411
+ langchain_agentx/tool_runtime/loop_loader.py,sha256=p_gDv9HWwPnO9KBRJWp8bNBuGq_-zh-yB8Ns0X3qQqA,9920
411
412
  langchain_agentx/tool_runtime/loop_runtime_context.py,sha256=HBZMdtKD7MpU8o7JmURFZGPTfxr2aFFlFF2nBZR_n8s,3613
412
413
  langchain_agentx/tool_runtime/messages.py,sha256=Fp-ZiLAbOQAQa8uIv-owE8PXtWyhpYSXxtzy3PVbKSw,3631
413
414
  langchain_agentx/tool_runtime/models.py,sha256=cO9EgLKPXiktHl_xXtBwLQrCo6mj4tbT4WoPmuFLE0w,20345
@@ -606,7 +607,7 @@ langchain_agentx/tools/shared/read_state_guard.py,sha256=YSDyACWwBGVl-GhpR7qDzYg
606
607
  langchain_agentx/tools/shared/staleness.py,sha256=wTSpnLdazuxTdbC5U7zNLdWxkMPq4F7M0TbrIBOBbKA,2519
607
608
  langchain_agentx/tools/skill/__init__.py,sha256=hNIgFeIcwpifU34Itwv5y25G4dvqBxY1XJEYfrOsBmc,132
608
609
  langchain_agentx/tools/skill/argument_substitution.py,sha256=xj_naOGCgPG7x9HAE2GzYsPQ50iDueWGjU02Oa3Ml98,2669
609
- langchain_agentx/tools/skill/catalog.py,sha256=CJKAKnFyFy5iuBNKpj6mwHZ3dVcOIJnJAk0OOWd9MbY,16377
610
+ langchain_agentx/tools/skill/catalog.py,sha256=kYCchsAgz32PQxkhRViewUpmaJW3mvRF1AhBEugGC88,16387
610
611
  langchain_agentx/tools/skill/dynamic_catalog.py,sha256=kc9tcAJMbvieIaO0SN89b58I0OIjPRsccKv8pLVoyXI,5409
611
612
  langchain_agentx/tools/skill/dynamic_discoverer.py,sha256=1zUcWHJn96UFJeDY9NPsjpZAJ3iQFAq_Y7REyUGXfMo,2978
612
613
  langchain_agentx/tools/skill/file_discovery.py,sha256=mEMSg5eqGbv0bU7ZWY_i7WdYZsL618pMo0wMrbunbSc,2202
@@ -615,14 +616,14 @@ langchain_agentx/tools/skill/guards.py,sha256=tO6hLncKW0eMpWLrdnZLeMzfknL0ufbE8N
615
616
  langchain_agentx/tools/skill/hook_registration.py,sha256=2V5srDxBFFnMVEvVbw1Rj39qcij4AtWPzgvL7--D72c,3739
616
617
  langchain_agentx/tools/skill/loader.py,sha256=E0P90vvja5thi-1nPT4OkWtOniBkISvumA5agV150XU,10086
617
618
  langchain_agentx/tools/skill/models.py,sha256=oo6hsDQtZth_xMetL38NzyxOrDQNzurmTgH8b55VV10,3144
618
- langchain_agentx/tools/skill/multi_source_repository.py,sha256=gP5dBWZ07mLFbIdsmrmL9rrdiarD1vtb26pgy2f35cc,3282
619
+ langchain_agentx/tools/skill/multi_source_repository.py,sha256=P1IRodDmnB1hNTqBcsbWvCFqesCSdE2mW-zhxee8dyE,3370
619
620
  langchain_agentx/tools/skill/paths_matcher.py,sha256=J4SOBThray_72mrYiQUfJ5Icu6TPwhctFLugXvMIEkI,3487
620
621
  langchain_agentx/tools/skill/plugin_loader.py,sha256=qT0VcIWkKFmRGGH8-e_f4y1XIpjY71TVbnZwnu2M9_M,1191
621
622
  langchain_agentx/tools/skill/policy.py,sha256=SFlW627gyo_-wyIppAqoytBmrLPlSGw-mbR00gjOJQY,1541
622
623
  langchain_agentx/tools/skill/prompt.py,sha256=tvSqvCGroVdloveiWIYxIPQmpESqzPAs5ZJztvHBdOI,1782
623
624
  langchain_agentx/tools/skill/registry_permission.py,sha256=ns_S6IBOdacrZcgHzCOj9rjlPOIecavF5hW-pHmalcY,3258
624
- langchain_agentx/tools/skill/repository_factory.py,sha256=7zDoPDZgxoR03CkmKdx9b0aUXwaH8E5LtBYQPGF26rA,1222
625
- langchain_agentx/tools/skill/source_paths.py,sha256=2CeVYF37JONiXrKQcogdBs0Y31p9AAMIOgGXZKnBQfE,3651
625
+ langchain_agentx/tools/skill/repository_factory.py,sha256=aFYIw65ctz1sLfEmn8E3YfpgTvTc1YI9XkueWpeiXtA,1359
626
+ langchain_agentx/tools/skill/source_paths.py,sha256=x-WHorUgt2xyI5hcvALT2e6Ra-HDpKgIv7plQezRkws,5324
626
627
  langchain_agentx/tools/skill/tool.py,sha256=vhiwjeyST9BhsMZmKk8-Vej3JId0AxTPrQ5ebFvguG4,19711
627
628
  langchain_agentx/tools/skill/bundled/__init__.py,sha256=gaCH_mROpv0lIgLjin1VIFu-8hbP2nY1PHoF7vZt2Ns,4516
628
629
  langchain_agentx/tools/skill/bundled/prompts.py,sha256=gcaTnNRlWfHNPgO3EfZxU9vMYbOapMrAduvgaVc-_Fc,4838
@@ -760,14 +761,14 @@ langchain_agentx/workspace/path_canonical.py,sha256=Z_mmDJQMP4WmBhjBpRUGKGg7XCsK
760
761
  langchain_agentx/workspace/path_key_normalizer.py,sha256=ICk9uGiiNFH-zaXJG3_vx91uOpgWmgOPIjJoSYevKOc,1113
761
762
  langchain_agentx/workspace/plans.py,sha256=wo0ogpErnmwBrNwsHHCzARnIfHM7xnhpAMiN39SjCCI,11813
762
763
  langchain_agentx/workspace/project_key.py,sha256=iA_C6oLM8ebWX6C4GXpJp-SO_n6O1heNcyNebC513QA,2808
763
- langchain_agentx/workspace/project_memory_layout.py,sha256=6B0FAfoqdIL_gWUmlaF-8w5OwKYhZP6P3TKZomv7F-A,7779
764
+ langchain_agentx/workspace/project_memory_layout.py,sha256=F-PlZpOCnXQGh24LF8nINgjkLmA6yTJ6tcdWzEzxL0U,8092
764
765
  langchain_agentx/workspace/resolver.py,sha256=stWBxa-jkNO2nZKEJr89Qb4805FcEFm76NpybWb-LYs,6444
765
766
  langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXOUBGr1Gjnl8CfCoTIuYc,7558
766
767
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
767
768
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
768
769
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
769
- langchain_agentx_python-2.3.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
770
- langchain_agentx_python-2.3.2.dist-info/METADATA,sha256=kIbTWeDzgAZS7u3vcxnCjP_NGMr1C6S3fUvdo_Ervw8,23942
771
- langchain_agentx_python-2.3.2.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
772
- langchain_agentx_python-2.3.2.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
773
- langchain_agentx_python-2.3.2.dist-info/RECORD,,
770
+ langchain_agentx_python-2.3.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
771
+ langchain_agentx_python-2.3.4.dist-info/METADATA,sha256=zMKaBp2iVa62_ASW-y0xHpoqDFuMu64NDF414ESG2XA,23942
772
+ langchain_agentx_python-2.3.4.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
773
+ langchain_agentx_python-2.3.4.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
774
+ langchain_agentx_python-2.3.4.dist-info/RECORD,,