langchain-agentx-python 0.1__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.
- langchain_agentx/__init__.py +46 -0
- langchain_agentx/command/__init__.py +28 -0
- langchain_agentx/command/builtin/__init__.py +25 -0
- langchain_agentx/command/builtin/clear.py +33 -0
- langchain_agentx/command/builtin/compact.py +33 -0
- langchain_agentx/command/builtin/memory.py +37 -0
- langchain_agentx/command/builtin/reload_plugins.py +42 -0
- langchain_agentx/command/context.py +30 -0
- langchain_agentx/command/dispatcher.py +183 -0
- langchain_agentx/command/registry.py +110 -0
- langchain_agentx/command/result.py +25 -0
- langchain_agentx/command/types.py +41 -0
- langchain_agentx/config/__init__.py +14 -0
- langchain_agentx/loop/__init__.py +47 -0
- langchain_agentx/loop/config/__init__.py +20 -0
- langchain_agentx/loop/config/agent_config.py +66 -0
- langchain_agentx/loop/config/agent_loop_config.py +72 -0
- langchain_agentx/loop/config/model_context_resolver.py +105 -0
- langchain_agentx/loop/config/runtime_settings.py +50 -0
- langchain_agentx/loop/config/token_estimator.py +133 -0
- langchain_agentx/loop/context/__init__.py +66 -0
- langchain_agentx/loop/context/blocking_guard.py +97 -0
- langchain_agentx/loop/context/compaction_service.py +60 -0
- langchain_agentx/loop/context/message_utils.py +56 -0
- langchain_agentx/loop/context/pipeline.py +127 -0
- langchain_agentx/loop/context/settings.py +103 -0
- langchain_agentx/loop/context/stages/__init__.py +29 -0
- langchain_agentx/loop/context/stages/autocompact.py +140 -0
- langchain_agentx/loop/context/stages/base.py +32 -0
- langchain_agentx/loop/context/stages/collapse.py +76 -0
- langchain_agentx/loop/context/stages/microcompact.py +76 -0
- langchain_agentx/loop/context/stages/noop.py +33 -0
- langchain_agentx/loop/context/stages/snip.py +71 -0
- langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
- langchain_agentx/loop/context/types.py +79 -0
- langchain_agentx/loop/exit/__init__.py +1 -0
- langchain_agentx/loop/exit/exit_logic.py +320 -0
- langchain_agentx/loop/exit/reason_codes.py +39 -0
- langchain_agentx/loop/graph/__init__.py +5 -0
- langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
- langchain_agentx/loop/graph/factory.py +1409 -0
- langchain_agentx/loop/graph/graph_edges.py +820 -0
- langchain_agentx/loop/hook/__init__.py +48 -0
- langchain_agentx/loop/hook/async_hook_runner.py +62 -0
- langchain_agentx/loop/hook/config.py +280 -0
- langchain_agentx/loop/hook/engine.py +321 -0
- langchain_agentx/loop/hook/executors/__init__.py +9 -0
- langchain_agentx/loop/hook/executors/agent.py +107 -0
- langchain_agentx/loop/hook/executors/command.py +230 -0
- langchain_agentx/loop/hook/executors/http.py +114 -0
- langchain_agentx/loop/hook/executors/prompt.py +92 -0
- langchain_agentx/loop/hook/graph_wiring.py +134 -0
- langchain_agentx/loop/hook/registry.py +262 -0
- langchain_agentx/loop/hook/trust.py +43 -0
- langchain_agentx/loop/hook/types.py +110 -0
- langchain_agentx/loop/injection/__init__.py +13 -0
- langchain_agentx/loop/injection/dedup.py +74 -0
- langchain_agentx/loop/loop_abort.py +36 -0
- langchain_agentx/loop/model/__init__.py +1 -0
- langchain_agentx/loop/model/model_node.py +648 -0
- langchain_agentx/loop/model/model_nodes.py +661 -0
- langchain_agentx/loop/model/orphan_tool_results.py +38 -0
- langchain_agentx/loop/model/retrier.py +307 -0
- langchain_agentx/loop/model/retry_bridge.py +58 -0
- langchain_agentx/loop/model/retry_events.py +35 -0
- langchain_agentx/loop/model/retry_policy.py +56 -0
- langchain_agentx/loop/model/schema_and_format.py +153 -0
- langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
- langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
- langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
- langchain_agentx/loop/prompt/__init__.py +95 -0
- langchain_agentx/loop/prompt/builder.py +61 -0
- langchain_agentx/loop/prompt/builtin.py +218 -0
- langchain_agentx/loop/prompt/compact.py +408 -0
- langchain_agentx/loop/prompt/sections.py +120 -0
- langchain_agentx/loop/runtime/__init__.py +19 -0
- langchain_agentx/loop/runtime/context.py +34 -0
- langchain_agentx/loop/runtime/context_factory.py +107 -0
- langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
- langchain_agentx/loop/subagent/__init__.py +53 -0
- langchain_agentx/loop/subagent/async_runner.py +215 -0
- langchain_agentx/loop/subagent/context.py +209 -0
- langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
- langchain_agentx/loop/subagent/graph.py +72 -0
- langchain_agentx/loop/subagent/orchestrator.py +391 -0
- langchain_agentx/loop/subagent/progress.py +30 -0
- langchain_agentx/loop/subagent/prompt.py +52 -0
- langchain_agentx/loop/subagent/runner.py +504 -0
- langchain_agentx/loop/subagent/transcript.py +172 -0
- langchain_agentx/memory/__init__.py +2 -0
- langchain_agentx/memory/instruction/__init__.py +12 -0
- langchain_agentx/memory/instruction/loader.py +325 -0
- langchain_agentx/memory/instruction/resolver.py +24 -0
- langchain_agentx/memory/instruction/runtime.py +83 -0
- langchain_agentx/memory/instruction/sections.py +83 -0
- langchain_agentx/memory/instruction/types.py +59 -0
- langchain_agentx/memory/memdir/__init__.py +77 -0
- langchain_agentx/memory/memdir/age.py +36 -0
- langchain_agentx/memory/memdir/agent_memory.py +380 -0
- langchain_agentx/memory/memdir/extractor.py +309 -0
- langchain_agentx/memory/memdir/loader.py +187 -0
- langchain_agentx/memory/memdir/paths.py +63 -0
- langchain_agentx/memory/memdir/recall.py +45 -0
- langchain_agentx/memory/memdir/runtime.py +43 -0
- langchain_agentx/memory/memdir/scan.py +135 -0
- langchain_agentx/memory/memdir/types.py +104 -0
- langchain_agentx/memory/session/__init__.py +76 -0
- langchain_agentx/memory/session/compact_bridge.py +208 -0
- langchain_agentx/memory/session/prompts.py +172 -0
- langchain_agentx/memory/session/session_memory.py +282 -0
- langchain_agentx/observability/__init__.py +67 -0
- langchain_agentx/observability/evaluation/__init__.py +17 -0
- langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
- langchain_agentx/observability/evaluation/checkers/base.py +34 -0
- langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
- langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
- langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
- langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
- langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
- langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
- langchain_agentx/observability/evaluation/service.py +102 -0
- langchain_agentx/observability/evaluation/state.py +32 -0
- langchain_agentx/observability/evaluation/store.py +258 -0
- langchain_agentx/observability/events/__init__.py +15 -0
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
- langchain_agentx/observability/logging/__init__.py +15 -0
- langchain_agentx/observability/logging/debug_burst.py +95 -0
- langchain_agentx/observability/logging/logging_config.py +178 -0
- langchain_agentx/observability/logging/logging_contract.py +65 -0
- langchain_agentx/observability/replay/__init__.py +35 -0
- langchain_agentx/observability/replay/cli.py +91 -0
- langchain_agentx/observability/replay/service.py +83 -0
- langchain_agentx/observability/replay/store.py +278 -0
- langchain_agentx/observability/replay/ui.py +47 -0
- langchain_agentx/observability/trace/__init__.py +25 -0
- langchain_agentx/observability/trace/collector.py +560 -0
- langchain_agentx/observability/trace/event_emitter.py +183 -0
- langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
- langchain_agentx/observability/trace/models.py +144 -0
- langchain_agentx/observability/trace/sqlite_store.py +873 -0
- langchain_agentx/observability/trace/trace_callback.py +295 -0
- langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
- langchain_agentx/plugin/__init__.py +26 -0
- langchain_agentx/plugin/builtin.py +53 -0
- langchain_agentx/plugin/config.py +113 -0
- langchain_agentx/plugin/loader.py +386 -0
- langchain_agentx/plugin/manifest.py +154 -0
- langchain_agentx/plugin/registries.py +211 -0
- langchain_agentx/plugin/types.py +142 -0
- langchain_agentx/provider/__init__.py +27 -0
- langchain_agentx/provider/anthropic.py +121 -0
- langchain_agentx/provider/compatible_chat_openai.py +86 -0
- langchain_agentx/provider/env.py +45 -0
- langchain_agentx/provider/model_profile.py +156 -0
- langchain_agentx/provider/openai.py +89 -0
- langchain_agentx/session/__init__.py +17 -0
- langchain_agentx/session/agent_session.py +320 -0
- langchain_agentx/session/conversation_factory.py +87 -0
- langchain_agentx/session/conversation_recovery.py +156 -0
- langchain_agentx/session/conversation_session.py +198 -0
- langchain_agentx/session/factory.py +143 -0
- langchain_agentx/session/protocol.py +25 -0
- langchain_agentx/task_runtime/__init__.py +113 -0
- langchain_agentx/task_runtime/core/__init__.py +51 -0
- langchain_agentx/task_runtime/core/ids.py +33 -0
- langchain_agentx/task_runtime/core/interfaces.py +115 -0
- langchain_agentx/task_runtime/core/notification_priority.py +19 -0
- langchain_agentx/task_runtime/core/types.py +136 -0
- langchain_agentx/task_runtime/integrations/__init__.py +33 -0
- langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
- langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
- langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
- langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
- langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
- langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
- langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
- langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
- langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
- langchain_agentx/task_runtime/output/__init__.py +5 -0
- langchain_agentx/task_runtime/output/sink.py +64 -0
- langchain_agentx/task_runtime/policy/__init__.py +11 -0
- langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
- langchain_agentx/task_runtime/queue/__init__.py +5 -0
- langchain_agentx/task_runtime/queue/in_memory.py +55 -0
- langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
- langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
- langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
- langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
- langchain_agentx/task_runtime/store/__init__.py +6 -0
- langchain_agentx/task_runtime/store/in_memory.py +81 -0
- langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
- langchain_agentx/task_runtime/tasks/__init__.py +76 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
- langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
- langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
- langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
- langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
- langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
- langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
- langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
- langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
- langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
- langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
- langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
- langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
- langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
- langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
- langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
- langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
- langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
- langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
- langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
- langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
- langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
- langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
- langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
- langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
- langchain_agentx/tool_runtime/__init__.py +90 -0
- langchain_agentx/tool_runtime/adapter.py +365 -0
- langchain_agentx/tool_runtime/base.py +319 -0
- langchain_agentx/tool_runtime/errors.py +190 -0
- langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
- langchain_agentx/tool_runtime/loader.py +195 -0
- langchain_agentx/tool_runtime/models.py +260 -0
- langchain_agentx/tool_runtime/permission_context.py +78 -0
- langchain_agentx/tool_runtime/pipeline.py +621 -0
- langchain_agentx/tool_runtime/policy.py +447 -0
- langchain_agentx/tool_runtime/registry.py +81 -0
- langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
- langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
- langchain_agentx/tool_runtime/resolvers/background.py +32 -0
- langchain_agentx/tool_runtime/resolvers/base.py +20 -0
- langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
- langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
- langchain_agentx/tool_runtime/session_store.py +132 -0
- langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
- langchain_agentx/tool_runtime/state_bridge.py +164 -0
- langchain_agentx/tools/__init__.py +26 -0
- langchain_agentx/tools/agent/__init__.py +9 -0
- langchain_agentx/tools/agent/backend.py +53 -0
- langchain_agentx/tools/agent/built_in/__init__.py +19 -0
- langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
- langchain_agentx/tools/agent/built_in/explore.py +80 -0
- langchain_agentx/tools/agent/built_in/general.py +57 -0
- langchain_agentx/tools/agent/built_in/plan.py +89 -0
- langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
- langchain_agentx/tools/agent/built_in/verification.py +120 -0
- langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
- langchain_agentx/tools/agent/cwd_resolution.py +119 -0
- langchain_agentx/tools/agent/limits.py +26 -0
- langchain_agentx/tools/agent/loader.py +270 -0
- langchain_agentx/tools/agent/models.py +85 -0
- langchain_agentx/tools/agent/prompt.py +120 -0
- langchain_agentx/tools/agent/registry/__init__.py +18 -0
- langchain_agentx/tools/agent/registry/config.py +29 -0
- langchain_agentx/tools/agent/registry/registry.py +47 -0
- langchain_agentx/tools/agent/scope.py +137 -0
- langchain_agentx/tools/agent/tool.py +256 -0
- langchain_agentx/tools/bash/__init__.py +9 -0
- langchain_agentx/tools/bash/ast_security.py +571 -0
- langchain_agentx/tools/bash/backend.py +1447 -0
- langchain_agentx/tools/bash/bash_hardening.py +734 -0
- langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
- langchain_agentx/tools/bash/cwd_reporter.py +95 -0
- langchain_agentx/tools/bash/limits.py +71 -0
- langchain_agentx/tools/bash/mode_validation.py +282 -0
- langchain_agentx/tools/bash/models.py +131 -0
- langchain_agentx/tools/bash/observability.py +148 -0
- langchain_agentx/tools/bash/output_utils.py +200 -0
- langchain_agentx/tools/bash/path_security.py +2429 -0
- langchain_agentx/tools/bash/prompt.py +68 -0
- langchain_agentx/tools/bash/read_only_validation.py +589 -0
- langchain_agentx/tools/bash/result_presenter.py +324 -0
- langchain_agentx/tools/bash/sandbox_decision.py +133 -0
- langchain_agentx/tools/bash/security.py +311 -0
- langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
- langchain_agentx/tools/bash/sed_validation.py +163 -0
- langchain_agentx/tools/bash/semantics.py +111 -0
- langchain_agentx/tools/bash/session_manager.py +205 -0
- langchain_agentx/tools/bash/session_runtime.py +290 -0
- langchain_agentx/tools/bash/shell_locator.py +191 -0
- langchain_agentx/tools/bash/task_runtime.py +91 -0
- langchain_agentx/tools/bash/tool.py +939 -0
- langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
- langchain_agentx/tools/glob/__init__.py +9 -0
- langchain_agentx/tools/glob/models.py +57 -0
- langchain_agentx/tools/glob/pagination.py +30 -0
- langchain_agentx/tools/glob/prompt.py +24 -0
- langchain_agentx/tools/glob/rg_list_backend.py +139 -0
- langchain_agentx/tools/glob/rg_pattern.py +44 -0
- langchain_agentx/tools/glob/tool.py +327 -0
- langchain_agentx/tools/grep/__init__.py +7 -0
- langchain_agentx/tools/grep/backend.py +375 -0
- langchain_agentx/tools/grep/models.py +127 -0
- langchain_agentx/tools/grep/prompt.py +30 -0
- langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
- langchain_agentx/tools/grep/tool.py +475 -0
- langchain_agentx/tools/read/__init__.py +9 -0
- langchain_agentx/tools/read/backend.py +415 -0
- langchain_agentx/tools/read/limits.py +67 -0
- langchain_agentx/tools/read/models.py +156 -0
- langchain_agentx/tools/read/prompt.py +73 -0
- langchain_agentx/tools/read/tool.py +494 -0
- langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
- langchain_agentx/tools/skill/__init__.py +4 -0
- langchain_agentx/tools/skill/argument_substitution.py +80 -0
- langchain_agentx/tools/skill/loader.py +196 -0
- langchain_agentx/tools/skill/models.py +88 -0
- langchain_agentx/tools/skill/policy.py +80 -0
- langchain_agentx/tools/skill/prompt.py +35 -0
- langchain_agentx/tools/skill/tool.py +222 -0
- langchain_agentx/utils/__init__.py +0 -0
- langchain_agentx/utils/cwd.py +124 -0
- langchain_agentx/utils/host_platform.py +112 -0
- langchain_agentx/utils/path_hierarchy.py +48 -0
- langchain_agentx/utils/path_user_input.py +66 -0
- langchain_agentx/utils/rg_executable.py +18 -0
- langchain_agentx/utils/subprocess_text.py +101 -0
- langchain_agentx/utils/temp_paths.py +77 -0
- langchain_agentx/utils/unc_path.py +25 -0
- langchain_agentx/utils/win_reserved_paths.py +51 -0
- langchain_agentx/workflow/__init__.py +7 -0
- langchain_agentx/workflow/base.py +97 -0
- langchain_agentx/workflow/batch.py +55 -0
- langchain_agentx/workflow/dag.py +54 -0
- langchain_agentx/workspace/__init__.py +13 -0
- langchain_agentx/workspace/config.py +140 -0
- langchain_agentx/workspace/path_key_normalizer.py +30 -0
- langchain_agentx/workspace/resolver.py +74 -0
- langchain_agentx/workspace/validators.py +41 -0
- langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
- langchain_agentx_python-0.1.dist-info/METADATA +513 -0
- langchain_agentx_python-0.1.dist-info/RECORD +354 -0
- langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
- langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""instruction/loader.py — Layer 1 规则加载编排。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
负责规则文件加载、@path 展开、frontmatter 路径解析与条件规则筛选。
|
|
5
|
+
|
|
6
|
+
链路位置:
|
|
7
|
+
Loop 初始化 -> load_rule_set(...) -> LoadedRuleSet -> sections 组装。
|
|
8
|
+
|
|
9
|
+
当前裁剪范围:
|
|
10
|
+
仅处理 Layer 1 文本加载与匹配,不解析规则业务语义。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from wcmatch import glob as wcglob
|
|
19
|
+
|
|
20
|
+
from langchain_agentx.utils.path_hierarchy import is_filesystem_root
|
|
21
|
+
|
|
22
|
+
from .resolver import resolve_rules_file_name
|
|
23
|
+
from .types import ConditionalRule, InstructionMemoryConfig, LoadedRuleSet, RuleFile, RuleLayer
|
|
24
|
+
|
|
25
|
+
_AT_PATH_RE = re.compile(r"(?:^|\s)@((?:[^\s\\]|\\ )+)")
|
|
26
|
+
_HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProjectRuleCollector:
|
|
30
|
+
"""Project 规则文件收集器(workspace_root 向上遍历)。"""
|
|
31
|
+
|
|
32
|
+
def collect(self, *, workspace_root: Path, agent_home: str, rules_file: str) -> list[Path]:
|
|
33
|
+
found: list[Path] = []
|
|
34
|
+
current = workspace_root.resolve()
|
|
35
|
+
while True:
|
|
36
|
+
candidate = current / agent_home / rules_file
|
|
37
|
+
if candidate.exists():
|
|
38
|
+
found.append(candidate)
|
|
39
|
+
if is_filesystem_root(current):
|
|
40
|
+
break
|
|
41
|
+
current = current.parent
|
|
42
|
+
found.reverse()
|
|
43
|
+
return found
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AtPathResolver:
|
|
47
|
+
"""`@path` 路径解析器(支持 ~/、绝对路径、fragment、转义空格)。"""
|
|
48
|
+
|
|
49
|
+
def resolve(self, *, raw: str, current_file: Path) -> Path:
|
|
50
|
+
normalized = raw.replace(r"\ ", " ")
|
|
51
|
+
normalized = normalized.split("#")[0]
|
|
52
|
+
p = Path(normalized)
|
|
53
|
+
if p.is_absolute():
|
|
54
|
+
return p.resolve()
|
|
55
|
+
if normalized.startswith("~/"):
|
|
56
|
+
return (Path.home() / normalized[2:]).resolve()
|
|
57
|
+
return (current_file.parent / p).resolve()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AtPathProcessor:
|
|
61
|
+
"""`@path` 行内展开器(代码块内跳过,循环/缺失文件保留原文)。"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, *, depth_limit: int, resolver: AtPathResolver | None = None) -> None:
|
|
64
|
+
self._depth_limit = depth_limit
|
|
65
|
+
self._resolver = resolver or AtPathResolver()
|
|
66
|
+
|
|
67
|
+
def process(
|
|
68
|
+
self,
|
|
69
|
+
*,
|
|
70
|
+
current_path: Path,
|
|
71
|
+
content: str,
|
|
72
|
+
visited: frozenset[Path],
|
|
73
|
+
depth: int,
|
|
74
|
+
) -> str:
|
|
75
|
+
if depth > self._depth_limit:
|
|
76
|
+
return content
|
|
77
|
+
|
|
78
|
+
lines: list[str] = []
|
|
79
|
+
in_code_block = False
|
|
80
|
+
for line in content.splitlines(keepends=True):
|
|
81
|
+
stripped = line.rstrip("\n\r")
|
|
82
|
+
if stripped.startswith("```") or stripped.startswith("~~~"):
|
|
83
|
+
in_code_block = not in_code_block
|
|
84
|
+
lines.append(line)
|
|
85
|
+
continue
|
|
86
|
+
if in_code_block:
|
|
87
|
+
lines.append(line)
|
|
88
|
+
continue
|
|
89
|
+
matches = _AT_PATH_RE.findall(stripped)
|
|
90
|
+
if not matches:
|
|
91
|
+
lines.append(line)
|
|
92
|
+
continue
|
|
93
|
+
result_line = line
|
|
94
|
+
for raw in matches:
|
|
95
|
+
resolved = self._resolver.resolve(raw=raw, current_file=current_path)
|
|
96
|
+
if resolved in visited:
|
|
97
|
+
continue
|
|
98
|
+
if not resolved.exists():
|
|
99
|
+
continue
|
|
100
|
+
included = resolved.read_text(encoding="utf-8")
|
|
101
|
+
expanded = self.process(
|
|
102
|
+
current_path=resolved,
|
|
103
|
+
content=included,
|
|
104
|
+
visited=visited | {resolved},
|
|
105
|
+
depth=depth + 1,
|
|
106
|
+
)
|
|
107
|
+
result_line = result_line.replace(f"@{raw}", expanded, 1)
|
|
108
|
+
lines.append(result_line)
|
|
109
|
+
return "".join(lines)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def strip_html_comments(content: str) -> str:
|
|
113
|
+
return _HTML_COMMENT_RE.sub('', content)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class FrontmatterPathsParser:
|
|
117
|
+
def parse(self, raw: str) -> tuple[str, list[str]]:
|
|
118
|
+
"""解析 frontmatter,返回 (去除 frontmatter 的正文, paths 列表)。"""
|
|
119
|
+
lines = raw.splitlines(keepends=True)
|
|
120
|
+
if len(lines) < 3 or lines[0].strip() != "---":
|
|
121
|
+
return raw, []
|
|
122
|
+
end_idx = None
|
|
123
|
+
for i in range(1, len(lines)):
|
|
124
|
+
if lines[i].strip() == "---":
|
|
125
|
+
end_idx = i
|
|
126
|
+
break
|
|
127
|
+
if end_idx is None:
|
|
128
|
+
return raw, []
|
|
129
|
+
|
|
130
|
+
fm_block = "".join(lines[1:end_idx])
|
|
131
|
+
content = "".join(lines[end_idx + 1:])
|
|
132
|
+
patterns = self._parse_paths_block(fm_block)
|
|
133
|
+
return content, patterns
|
|
134
|
+
|
|
135
|
+
def parse_paths(self, raw: str) -> list[str]:
|
|
136
|
+
_, patterns = self.parse(raw)
|
|
137
|
+
return patterns
|
|
138
|
+
|
|
139
|
+
def _parse_paths_block(self, fm_block: str) -> list[str]:
|
|
140
|
+
fm_lines = fm_block.splitlines()
|
|
141
|
+
in_paths = False
|
|
142
|
+
patterns: list[str] = []
|
|
143
|
+
for line in fm_lines:
|
|
144
|
+
stripped = line.strip()
|
|
145
|
+
if not stripped:
|
|
146
|
+
continue
|
|
147
|
+
if stripped.startswith("paths:"):
|
|
148
|
+
direct = stripped[len("paths:"):].strip()
|
|
149
|
+
if direct:
|
|
150
|
+
patterns.extend(self._split_inline_paths(direct))
|
|
151
|
+
in_paths = True
|
|
152
|
+
continue
|
|
153
|
+
if in_paths:
|
|
154
|
+
if stripped.startswith("- "):
|
|
155
|
+
val = stripped[2:].strip().strip("'").strip('"')
|
|
156
|
+
if val:
|
|
157
|
+
patterns.append(val)
|
|
158
|
+
else:
|
|
159
|
+
in_paths = False
|
|
160
|
+
return patterns
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _split_inline_paths(raw: str) -> list[str]:
|
|
164
|
+
# 支持 paths: "a,b" 或 paths: a,b;逗号分隔时忽略空白项。
|
|
165
|
+
normalized = raw.strip().strip("'").strip('"')
|
|
166
|
+
if not normalized:
|
|
167
|
+
return []
|
|
168
|
+
return [part.strip() for part in normalized.split(",") if part.strip()]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _path_matches_pattern(file_path: str, pattern: str) -> bool:
|
|
172
|
+
return wcglob.globmatch(file_path, pattern, flags=wcglob.GLOBSTAR)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _parse_paths_from_frontmatter(raw: str) -> list[str]:
|
|
176
|
+
return FrontmatterPathsParser().parse_paths(raw)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_active_conditional_rules(
|
|
180
|
+
conditional_rules: list[ConditionalRule],
|
|
181
|
+
active_files: list[str],
|
|
182
|
+
) -> list[ConditionalRule]:
|
|
183
|
+
result: list[ConditionalRule] = []
|
|
184
|
+
for rule in conditional_rules:
|
|
185
|
+
if not rule.paths_patterns:
|
|
186
|
+
result.append(rule)
|
|
187
|
+
continue
|
|
188
|
+
if any(
|
|
189
|
+
_path_matches_pattern(f, pattern)
|
|
190
|
+
for f in active_files
|
|
191
|
+
for pattern in rule.paths_patterns
|
|
192
|
+
):
|
|
193
|
+
result.append(rule)
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class InstructionRuleLoader:
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
*,
|
|
201
|
+
cfg: InstructionMemoryConfig,
|
|
202
|
+
frontmatter_parser: FrontmatterPathsParser | None = None,
|
|
203
|
+
project_collector: ProjectRuleCollector | None = None,
|
|
204
|
+
at_path_processor: AtPathProcessor | None = None,
|
|
205
|
+
) -> None:
|
|
206
|
+
self._cfg = cfg
|
|
207
|
+
self._frontmatter_parser = frontmatter_parser or FrontmatterPathsParser()
|
|
208
|
+
self._project_collector = project_collector or ProjectRuleCollector()
|
|
209
|
+
self._at_path_processor = at_path_processor or AtPathProcessor(
|
|
210
|
+
depth_limit=cfg.include_depth_limit
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def load(self) -> LoadedRuleSet:
|
|
214
|
+
ws = self._cfg.workspace_cfg
|
|
215
|
+
rules_file, local_file, rules_dir_name = resolve_rules_file_name(ws.agent_home)
|
|
216
|
+
|
|
217
|
+
user_rf: RuleFile | None = None
|
|
218
|
+
if self._cfg.load_user_layer:
|
|
219
|
+
user_path = Path.home() / ws.agent_home / rules_file
|
|
220
|
+
if user_path.exists():
|
|
221
|
+
user_rf = self._load_file(user_path, RuleLayer.USER)
|
|
222
|
+
|
|
223
|
+
project_paths = self._project_collector.collect(
|
|
224
|
+
workspace_root=ws.workspace_root,
|
|
225
|
+
agent_home=ws.agent_home,
|
|
226
|
+
rules_file=rules_file,
|
|
227
|
+
)
|
|
228
|
+
project_files = [
|
|
229
|
+
self._load_file(p, RuleLayer.PROJECT) for p in project_paths
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
local_path = ws.agent_home_dir / local_file
|
|
233
|
+
local_rf = self._load_file(local_path, RuleLayer.LOCAL) if local_path.exists() else None
|
|
234
|
+
|
|
235
|
+
rules_dir = ws.agent_home_dir / rules_dir_name
|
|
236
|
+
conditional = self._load_conditional_rules(rules_dir)
|
|
237
|
+
|
|
238
|
+
return LoadedRuleSet(
|
|
239
|
+
managed=self._cfg.managed_rules,
|
|
240
|
+
user=user_rf,
|
|
241
|
+
project_files=project_files,
|
|
242
|
+
local=local_rf,
|
|
243
|
+
conditional_rules=conditional,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def _load_file(self, path: Path, layer: RuleLayer) -> RuleFile:
|
|
247
|
+
raw = path.read_text(encoding="utf-8")
|
|
248
|
+
content_wo_frontmatter, _ = self._frontmatter_parser.parse(raw)
|
|
249
|
+
content = self._at_path_processor.process(
|
|
250
|
+
current_path=path,
|
|
251
|
+
content=content_wo_frontmatter,
|
|
252
|
+
visited=frozenset([path.resolve()]),
|
|
253
|
+
depth=0,
|
|
254
|
+
)
|
|
255
|
+
return RuleFile(path=path, content=content, source_layer=layer)
|
|
256
|
+
|
|
257
|
+
def _load_conditional_rules(self, rules_dir: Path) -> list[ConditionalRule]:
|
|
258
|
+
if not rules_dir.exists() or not rules_dir.is_dir():
|
|
259
|
+
return []
|
|
260
|
+
result: list[ConditionalRule] = []
|
|
261
|
+
for file_path in sorted(rules_dir.glob("*.md"), key=lambda p: p.name):
|
|
262
|
+
raw = file_path.read_text(encoding="utf-8")
|
|
263
|
+
content_wo_frontmatter, patterns = self._frontmatter_parser.parse(raw)
|
|
264
|
+
content = self._at_path_processor.process(
|
|
265
|
+
current_path=file_path,
|
|
266
|
+
content=content_wo_frontmatter,
|
|
267
|
+
visited=frozenset([file_path.resolve()]),
|
|
268
|
+
depth=0,
|
|
269
|
+
)
|
|
270
|
+
result.append(ConditionalRule(path=file_path, content=content, paths_patterns=patterns))
|
|
271
|
+
return result
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def load_rule_set(cfg: InstructionMemoryConfig) -> LoadedRuleSet:
|
|
275
|
+
return InstructionRuleLoader(cfg=cfg).load()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def load_conditional_rules(rules_dir: Path, depth_limit: int) -> list[ConditionalRule]:
|
|
279
|
+
if not rules_dir.exists() or not rules_dir.is_dir():
|
|
280
|
+
return []
|
|
281
|
+
parser = FrontmatterPathsParser()
|
|
282
|
+
processor = AtPathProcessor(depth_limit=depth_limit)
|
|
283
|
+
result: list[ConditionalRule] = []
|
|
284
|
+
for file_path in sorted(rules_dir.glob("*.md"), key=lambda p: p.name):
|
|
285
|
+
raw = file_path.read_text(encoding="utf-8")
|
|
286
|
+
content_wo_frontmatter, patterns = parser.parse(raw)
|
|
287
|
+
content = processor.process(
|
|
288
|
+
current_path=file_path,
|
|
289
|
+
content=content_wo_frontmatter,
|
|
290
|
+
visited=frozenset([file_path.resolve()]),
|
|
291
|
+
depth=0,
|
|
292
|
+
)
|
|
293
|
+
result.append(ConditionalRule(path=file_path, content=content, paths_patterns=patterns))
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def collect_project_rule_files(
|
|
298
|
+
workspace_root: Path,
|
|
299
|
+
agent_home: str,
|
|
300
|
+
rules_file: str,
|
|
301
|
+
) -> list[Path]:
|
|
302
|
+
return ProjectRuleCollector().collect(
|
|
303
|
+
workspace_root=workspace_root,
|
|
304
|
+
agent_home=agent_home,
|
|
305
|
+
rules_file=rules_file,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _resolve_at_path(raw: str, current_file: Path) -> Path:
|
|
310
|
+
return AtPathResolver().resolve(raw=raw, current_file=current_file)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def process_at_paths(
|
|
314
|
+
current_path: Path,
|
|
315
|
+
content: str,
|
|
316
|
+
visited: frozenset[Path],
|
|
317
|
+
depth: int,
|
|
318
|
+
depth_limit: int,
|
|
319
|
+
) -> str:
|
|
320
|
+
return AtPathProcessor(depth_limit=depth_limit).process(
|
|
321
|
+
current_path=current_path,
|
|
322
|
+
content=content,
|
|
323
|
+
visited=visited,
|
|
324
|
+
depth=depth,
|
|
325
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""instruction/resolver.py — Layer 1 规则文件名解析器。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
基于 agent_home 返回主规则文件、本地规则文件、rules 目录名映射。
|
|
5
|
+
|
|
6
|
+
链路位置:
|
|
7
|
+
InstructionRuleLoader.load() -> resolve_rules_file_name(...)。
|
|
8
|
+
|
|
9
|
+
当前裁剪范围:
|
|
10
|
+
仅做名称映射,不执行目录扫描与存在性回退。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
_AGENT_HOME_MAP: dict[str, tuple[str, str, str]] = {
|
|
16
|
+
".claude": ("CLAUDE.md", "CLAUDE.local.md", "rules"),
|
|
17
|
+
".cursor": (".cursorrules", ".cursorrules.local", "rules"),
|
|
18
|
+
}
|
|
19
|
+
_DEFAULT = ("AGENT.md", "AGENT.local.md", "rules")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_rules_file_name(agent_home: str) -> tuple[str, str, str]:
|
|
23
|
+
return _AGENT_HOME_MAP.get(agent_home, _DEFAULT)
|
|
24
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""instruction/runtime.py — Layer 1 在 loop 侧的接线协作者。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
封装 Instruction Memory 在运行时的配置解析与 section 构建,避免工厂内联拼装。
|
|
5
|
+
|
|
6
|
+
链路位置:
|
|
7
|
+
LoopGraphBuilder.__init__ -> InstructionMemoryBootstrap.build_sections()。
|
|
8
|
+
|
|
9
|
+
当前裁剪范围:
|
|
10
|
+
提供默认 active_files 提供器(空列表),具体会话采集策略由上层注入。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from langchain_agentx.loop.prompt import SystemPromptSection
|
|
19
|
+
from langchain_agentx.workspace import resolve_agent_workspace_config
|
|
20
|
+
|
|
21
|
+
from .loader import load_rule_set
|
|
22
|
+
from .sections import make_instruction_sections
|
|
23
|
+
from .types import InstructionMemoryConfig
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InstructionMemoryBootstrap:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
workspace_root: str | Path,
|
|
31
|
+
agent_home: str,
|
|
32
|
+
managed_rules: str | None,
|
|
33
|
+
include_depth_limit: int = 5,
|
|
34
|
+
active_files_provider: "ActiveFilesProvider | None" = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
self._workspace_root = workspace_root
|
|
37
|
+
self._agent_home = agent_home
|
|
38
|
+
self._managed_rules = managed_rules
|
|
39
|
+
self._include_depth_limit = include_depth_limit
|
|
40
|
+
self._active_files_provider = active_files_provider or NullActiveFilesProvider()
|
|
41
|
+
|
|
42
|
+
def build_sections(self) -> list[SystemPromptSection]:
|
|
43
|
+
workspace_cfg = resolve_agent_workspace_config(
|
|
44
|
+
workspace_root=self._workspace_root,
|
|
45
|
+
agent_home=self._agent_home,
|
|
46
|
+
)
|
|
47
|
+
loaded_rules = load_rule_set(
|
|
48
|
+
InstructionMemoryConfig(
|
|
49
|
+
workspace_cfg=workspace_cfg,
|
|
50
|
+
managed_rules=self._managed_rules,
|
|
51
|
+
load_user_layer=True,
|
|
52
|
+
include_depth_limit=self._include_depth_limit,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
return make_instruction_sections(
|
|
56
|
+
loaded_rules,
|
|
57
|
+
active_files_getter=self._active_files_provider.get_active_files,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ActiveFilesProvider:
|
|
62
|
+
"""active_files 提供器接口。"""
|
|
63
|
+
|
|
64
|
+
def get_active_files(self) -> list[str]:
|
|
65
|
+
raise NotImplementedError()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NullActiveFilesProvider(ActiveFilesProvider):
|
|
69
|
+
"""默认 provider:无会话文件上下文时返回空列表。"""
|
|
70
|
+
|
|
71
|
+
def get_active_files(self) -> list[str]:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CallbackActiveFilesProvider(ActiveFilesProvider):
|
|
76
|
+
"""通过回调函数桥接外部会话上下文。"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, getter: Callable[[], list[str]]) -> None:
|
|
79
|
+
self._getter = getter
|
|
80
|
+
|
|
81
|
+
def get_active_files(self) -> list[str]:
|
|
82
|
+
return list(self._getter() or [])
|
|
83
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""instruction/sections.py — Layer 1 规则段装配器。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
将 LoadedRuleSet 按静态/动态规则拆分为可缓存的 prompt sections。
|
|
5
|
+
|
|
6
|
+
链路位置:
|
|
7
|
+
load_rule_set(...) -> make_instruction_sections(...) -> build_effective_system_prompt(...)。
|
|
8
|
+
|
|
9
|
+
当前裁剪范围:
|
|
10
|
+
仅负责文本组装,不负责文件加载与 active_files 维护。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
|
|
17
|
+
from langchain_agentx.loop.prompt import SystemPromptSection, section, volatile_section
|
|
18
|
+
|
|
19
|
+
from .loader import get_active_conditional_rules, strip_html_comments
|
|
20
|
+
from .types import LoadedRuleSet
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class InstructionSectionBuilder:
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
rule_set: LoadedRuleSet,
|
|
28
|
+
active_files_getter: Callable[[], list[str]],
|
|
29
|
+
) -> None:
|
|
30
|
+
self._rule_set = rule_set
|
|
31
|
+
self._active_files_getter = active_files_getter
|
|
32
|
+
|
|
33
|
+
def build_static_rules(self) -> str | None:
|
|
34
|
+
parts: list[str] = []
|
|
35
|
+
if self._rule_set.managed:
|
|
36
|
+
parts.append(strip_html_comments(self._rule_set.managed))
|
|
37
|
+
if self._rule_set.user:
|
|
38
|
+
parts.append(strip_html_comments(self._rule_set.user.content))
|
|
39
|
+
for rf in self._rule_set.project_files:
|
|
40
|
+
parts.append(strip_html_comments(rf.content))
|
|
41
|
+
if self._rule_set.local:
|
|
42
|
+
parts.append(strip_html_comments(self._rule_set.local.content))
|
|
43
|
+
for rule in self._rule_set.conditional_rules:
|
|
44
|
+
if not rule.paths_patterns:
|
|
45
|
+
parts.append(strip_html_comments(rule.content))
|
|
46
|
+
return "".join(parts) if parts else None
|
|
47
|
+
|
|
48
|
+
def build_conditional_rules(self) -> str | None:
|
|
49
|
+
dynamic_rules = [
|
|
50
|
+
r
|
|
51
|
+
for r in get_active_conditional_rules(
|
|
52
|
+
self._rule_set.conditional_rules,
|
|
53
|
+
self._active_files_getter(),
|
|
54
|
+
)
|
|
55
|
+
if r.paths_patterns
|
|
56
|
+
]
|
|
57
|
+
if not dynamic_rules:
|
|
58
|
+
return None
|
|
59
|
+
return "".join(strip_html_comments(r.content) for r in dynamic_rules)
|
|
60
|
+
|
|
61
|
+
def build_sections(self) -> list[SystemPromptSection]:
|
|
62
|
+
return [
|
|
63
|
+
section(
|
|
64
|
+
name="agent_rules_static",
|
|
65
|
+
compute=self.build_static_rules,
|
|
66
|
+
),
|
|
67
|
+
volatile_section(
|
|
68
|
+
name="agent_rules_conditional",
|
|
69
|
+
compute=self.build_conditional_rules,
|
|
70
|
+
reason="active_files changes as session progresses; paths matching must re-evaluate each turn",
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def make_instruction_sections(
|
|
76
|
+
rule_set: LoadedRuleSet,
|
|
77
|
+
active_files_getter: Callable[[], list[str]],
|
|
78
|
+
) -> list[SystemPromptSection]:
|
|
79
|
+
return InstructionSectionBuilder(
|
|
80
|
+
rule_set=rule_set,
|
|
81
|
+
active_files_getter=active_files_getter,
|
|
82
|
+
).build_sections()
|
|
83
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""instruction/types.py — Layer 1 指令规则核心数据模型。
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
定义规则来源层、单文件结果、条件规则、完整加载结果与配置入口。
|
|
5
|
+
|
|
6
|
+
链路位置:
|
|
7
|
+
resolver/loader/sections 之间共享的契约层。
|
|
8
|
+
|
|
9
|
+
当前裁剪范围:
|
|
10
|
+
仅数据建模,不包含任何 IO 或匹配逻辑。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from langchain_agentx.workspace import AgentWorkspaceConfig
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RuleLayer(str, Enum):
|
|
23
|
+
MANAGED = "managed"
|
|
24
|
+
USER = "user"
|
|
25
|
+
PROJECT = "project"
|
|
26
|
+
RULES = "rules"
|
|
27
|
+
LOCAL = "local"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class RuleFile:
|
|
32
|
+
path: Path | None
|
|
33
|
+
content: str
|
|
34
|
+
source_layer: RuleLayer
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class ConditionalRule:
|
|
39
|
+
path: Path
|
|
40
|
+
content: str
|
|
41
|
+
paths_patterns: list[str]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class LoadedRuleSet:
|
|
46
|
+
managed: str | None
|
|
47
|
+
user: RuleFile | None
|
|
48
|
+
project_files: list[RuleFile] # root → workspace_root order; workspace_root has highest priority
|
|
49
|
+
local: RuleFile | None
|
|
50
|
+
conditional_rules: list[ConditionalRule]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class InstructionMemoryConfig:
|
|
55
|
+
workspace_cfg: AgentWorkspaceConfig
|
|
56
|
+
managed_rules: str | None = None
|
|
57
|
+
load_user_layer: bool = True
|
|
58
|
+
include_depth_limit: int = 5
|
|
59
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""memdir — Layer 2 跨会话事实记忆能力入口。"""
|
|
2
|
+
|
|
3
|
+
from .age import memory_age_days, memory_freshness_text
|
|
4
|
+
from .agent_memory import (
|
|
5
|
+
AgentMemoryExtractionContext,
|
|
6
|
+
AgentMemoryExtractionCoordinator,
|
|
7
|
+
AgentMemoryPromptBootstrap,
|
|
8
|
+
AgentMemoryPromptService,
|
|
9
|
+
AgentMemoryPathPolicy,
|
|
10
|
+
drain_pending_agent_memory_extraction,
|
|
11
|
+
get_default_agent_memory_extraction_coordinator,
|
|
12
|
+
is_agent_memory_path,
|
|
13
|
+
is_agent_memory_path_for_type,
|
|
14
|
+
load_agent_memory_prompt,
|
|
15
|
+
sanitize_agent_type,
|
|
16
|
+
)
|
|
17
|
+
from .extractor import (
|
|
18
|
+
MemoryExtractionContext,
|
|
19
|
+
MemoryExtractionCoordinator,
|
|
20
|
+
build_extract_prompt,
|
|
21
|
+
create_auto_mem_can_use_tool,
|
|
22
|
+
drain_pending_extraction,
|
|
23
|
+
execute_extract_memories,
|
|
24
|
+
)
|
|
25
|
+
from .loader import (
|
|
26
|
+
EntrypointTruncation,
|
|
27
|
+
MemoryPromptLoader,
|
|
28
|
+
build_memory_lines,
|
|
29
|
+
load_memory_prompt,
|
|
30
|
+
truncate_entrypoint_content,
|
|
31
|
+
)
|
|
32
|
+
from .paths import get_memory_dir, is_memory_enabled, is_memory_path
|
|
33
|
+
from .runtime import MemoryPromptBootstrap
|
|
34
|
+
from .scan import MAX_SCAN_FILES, MemoryFileHeader, MemoryScanner, format_memory_manifest, scan_memory_files
|
|
35
|
+
from .recall import find_relevant_memories, select_relevant_memories
|
|
36
|
+
from .types import MEMORY_TYPES, MemoryType, parse_memory_type
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"EntrypointTruncation",
|
|
40
|
+
"AgentMemoryPathPolicy",
|
|
41
|
+
"AgentMemoryExtractionContext",
|
|
42
|
+
"AgentMemoryExtractionCoordinator",
|
|
43
|
+
"AgentMemoryPromptBootstrap",
|
|
44
|
+
"AgentMemoryPromptService",
|
|
45
|
+
"get_default_agent_memory_extraction_coordinator",
|
|
46
|
+
"MEMORY_TYPES",
|
|
47
|
+
"MAX_SCAN_FILES",
|
|
48
|
+
"MemoryExtractionContext",
|
|
49
|
+
"MemoryExtractionCoordinator",
|
|
50
|
+
"MemoryFileHeader",
|
|
51
|
+
"build_extract_prompt",
|
|
52
|
+
"create_auto_mem_can_use_tool",
|
|
53
|
+
"drain_pending_extraction",
|
|
54
|
+
"execute_extract_memories",
|
|
55
|
+
"MemoryScanner",
|
|
56
|
+
"MemoryType",
|
|
57
|
+
"MemoryPromptBootstrap",
|
|
58
|
+
"MemoryPromptLoader",
|
|
59
|
+
"build_memory_lines",
|
|
60
|
+
"format_memory_manifest",
|
|
61
|
+
"get_memory_dir",
|
|
62
|
+
"is_memory_enabled",
|
|
63
|
+
"is_memory_path",
|
|
64
|
+
"is_agent_memory_path",
|
|
65
|
+
"is_agent_memory_path_for_type",
|
|
66
|
+
"load_agent_memory_prompt",
|
|
67
|
+
"drain_pending_agent_memory_extraction",
|
|
68
|
+
"load_memory_prompt",
|
|
69
|
+
"memory_age_days",
|
|
70
|
+
"memory_freshness_text",
|
|
71
|
+
"parse_memory_type",
|
|
72
|
+
"sanitize_agent_type",
|
|
73
|
+
"find_relevant_memories",
|
|
74
|
+
"scan_memory_files",
|
|
75
|
+
"select_relevant_memories",
|
|
76
|
+
"truncate_entrypoint_content",
|
|
77
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
memory/memdir/age.py — Layer 2 记忆新鲜度文本。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
基于文件更新时间计算 age 文本,为 memory manifest 提供可读提示。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
scan/build_memory_lines 在组装输出时读取本模块结果。
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅基于 mtime,不引入时区数据库或复杂本地化。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def memory_age_days(path: Path, now: datetime | None = None) -> int:
|
|
21
|
+
snapshot = now or datetime.now()
|
|
22
|
+
mtime = datetime.fromtimestamp(path.stat().st_mtime)
|
|
23
|
+
delta = snapshot - mtime
|
|
24
|
+
return max(0, delta.days)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def memory_freshness_text(path: Path, now: datetime | None = None) -> str:
|
|
28
|
+
days = memory_age_days(path, now=now)
|
|
29
|
+
if days <= 0:
|
|
30
|
+
return "updated today"
|
|
31
|
+
if days == 1:
|
|
32
|
+
return "updated yesterday"
|
|
33
|
+
return f"updated {days} days ago"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["memory_age_days", "memory_freshness_text"]
|