tactus 0.39.0__py3-none-any.whl → 0.40.0__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.
tactus/__init__.py CHANGED
@@ -5,7 +5,7 @@ Tactus provides a declarative workflow engine for AI agents with pluggable
5
5
  backends for storage, HITL, and chat recording.
6
6
  """
7
7
 
8
- __version__ = "0.39.0"
8
+ __version__ = "0.40.0"
9
9
 
10
10
  # Core exports
11
11
  from tactus.core.runtime import TactusRuntime
tactus/cli/app.py CHANGED
@@ -505,6 +505,12 @@ def run(
505
505
  real: Optional[list[str]] = typer.Option(
506
506
  None, "--real", help="Use real implementation for specific tool(s)"
507
507
  ),
508
+ auto_deps: bool = typer.Option(
509
+ False, "--auto-deps", help="Automatically run dependency tasks without prompting"
510
+ ),
511
+ no_deps: bool = typer.Option(
512
+ False, "--no-deps", help="Fail fast if dependency tasks are required"
513
+ ),
508
514
  sandbox: Optional[bool] = typer.Option(
509
515
  None,
510
516
  "--sandbox/--no-sandbox",
@@ -568,6 +574,15 @@ def run(
568
574
  console.print(f"[red]Error:[/red] Workflow file not found: {workflow_file}")
569
575
  raise typer.Exit(1)
570
576
 
577
+ if not isinstance(auto_deps, bool):
578
+ auto_deps = False
579
+ if not isinstance(no_deps, bool):
580
+ no_deps = False
581
+
582
+ if auto_deps and no_deps:
583
+ console.print("[red]Error:[/red] --auto-deps and --no-deps cannot be combined")
584
+ raise typer.Exit(1)
585
+
571
586
  # Determine format based on extension
572
587
  file_format = "lua" if workflow_file.suffix in [".tac", ".lua"] else "yaml"
573
588
 
@@ -808,6 +823,15 @@ def run(
808
823
  tool_paths=tool_paths,
809
824
  source_file_path=str(workflow_file),
810
825
  )
826
+ runtime.dependency_mode = "auto" if auto_deps else "none" if no_deps else "prompt"
827
+
828
+ def _dependency_prompt_handler(plan, label: str) -> bool:
829
+ pending = [task.kind for task in plan.tasks if task.status != "complete"]
830
+ pending_summary = ", ".join(pending) if pending else "none"
831
+ console.print(f"[yellow]Dependencies required for {label}: {pending_summary}[/yellow]")
832
+ return typer.confirm("Run dependencies now?", default=False)
833
+
834
+ runtime.dependency_prompt_handler = _dependency_prompt_handler
811
835
 
812
836
  # Always create a mock manager so Mocks {} blocks can register tool mocks.
813
837
  from tactus.core.mocking import MockManager, set_current_mock_manager
tactus/core/dsl_stubs.py CHANGED
@@ -461,7 +461,13 @@ def create_dsl_stubs(
461
461
  if hasattr(task_config, "items"):
462
462
  child_tasks = {}
463
463
  for key, value in task_config.items():
464
- if isinstance(key, str) and hasattr(value, "items"):
464
+ if not (isinstance(key, str) and hasattr(value, "items")):
465
+ continue
466
+ try:
467
+ marker = value["__tactus_task_config"]
468
+ except Exception:
469
+ marker = False
470
+ if marker:
465
471
  child_tasks[key] = value
466
472
  if child_tasks:
467
473
  try:
tactus/core/runtime.py CHANGED
@@ -14,7 +14,7 @@ import logging
14
14
  import time
15
15
  import uuid
16
16
  from pathlib import Path
17
- from typing import Any, Dict, List, Optional
17
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
18
18
 
19
19
  from tactus.core.registry import ProcedureRegistry, RegistryBuilder, TaskDeclaration
20
20
  from tactus.core.dsl_stubs import create_dsl_stubs, lua_table_to_dict
@@ -28,6 +28,9 @@ from tactus.protocols.storage import StorageBackend
28
28
  from tactus.protocols.hitl import HITLHandler
29
29
  from tactus.protocols.chat_recorder import ChatRecorder
30
30
 
31
+ if TYPE_CHECKING:
32
+ from biblicus.corpus import Corpus
33
+
31
34
  # For backwards compatibility with YAML
32
35
  try:
33
36
  from tactus.core.yaml_parser import ProcedureYAMLParser, ProcedureConfigError
@@ -142,6 +145,8 @@ class TactusRuntime:
142
145
  self.tool_paths = tool_paths or []
143
146
  self.recursion_depth = recursion_depth
144
147
  self.external_config = external_config or {}
148
+ self.dependency_mode = self.external_config.get("dependency_mode", "prompt")
149
+ self.dependency_prompt_handler = None
145
150
  self.run_id = run_id
146
151
  self.source_file_path = source_file_path
147
152
 
@@ -2552,6 +2557,7 @@ class TactusRuntime:
2552
2557
  return self._execute_task(self.task_name)
2553
2558
 
2554
2559
  if self.registry:
2560
+ self._execute_run_dependencies()
2555
2561
  # Check for named 'main' procedure first
2556
2562
  if "main" in self.registry.named_procedures:
2557
2563
  logger.info("Executing named 'main' procedure")
@@ -2639,10 +2645,169 @@ class TactusRuntime:
2639
2645
  logger.error(f"Legacy procedure execution failed: {e}")
2640
2646
  raise
2641
2647
 
2642
- def _execute_task(self, task_name: str) -> Any:
2648
+ def _parse_task_dependencies(self, task_payload: dict[str, Any]) -> list[str]:
2649
+ depends_on = task_payload.get("depends_on")
2650
+ if isinstance(depends_on, str):
2651
+ return [depends_on]
2652
+ if isinstance(depends_on, list):
2653
+ return [item for item in depends_on if isinstance(item, str) and item.strip()]
2654
+ return []
2655
+
2656
+ def _parse_task_provides(self, task_payload: dict[str, Any]) -> list[dict[str, Any]]:
2657
+ provides = task_payload.get("provides")
2658
+ if isinstance(provides, dict):
2659
+ return [provides]
2660
+ if isinstance(provides, list):
2661
+ return [item for item in provides if isinstance(item, dict)]
2662
+ return []
2663
+
2664
+ def _iter_task_declarations(self) -> list[tuple[str, TaskDeclaration]]:
2665
+ if not self.registry:
2666
+ return []
2667
+ tasks = getattr(self.registry, "tasks", None) or {}
2668
+ if not tasks:
2669
+ return []
2670
+
2671
+ def walk(task: TaskDeclaration, prefix: str) -> list[tuple[str, TaskDeclaration]]:
2672
+ full_name = f"{prefix}:{task.name}" if prefix else task.name
2673
+ pairs = [(full_name, task)]
2674
+ for child in task.children.values():
2675
+ pairs.extend(walk(child, full_name))
2676
+ return pairs
2677
+
2678
+ pairs: list[tuple[str, TaskDeclaration]] = []
2679
+ for task in tasks.values():
2680
+ pairs.extend(walk(task, ""))
2681
+ return pairs
2682
+
2683
+ def _find_task_providing(self, *, kind: str, corpus_name: Optional[str]) -> Optional[str]:
2684
+ if not self.registry:
2685
+ return None
2686
+ matches: list[str] = []
2687
+ normalize_task_kind = self._normalize_task_kind
2688
+
2689
+ for full_name, task in self._iter_task_declarations():
2690
+ payload = task.model_dump()
2691
+ for provides in self._parse_task_provides(payload):
2692
+ provided_kind = provides.get("kind")
2693
+ provided_corpus = provides.get("corpus")
2694
+ if not isinstance(provided_kind, str):
2695
+ continue
2696
+ if normalize_task_kind(provided_kind) != kind:
2697
+ continue
2698
+ if provided_corpus is None or provided_corpus == corpus_name:
2699
+ matches.append(full_name)
2700
+ if matches:
2701
+ return matches[0]
2702
+ if kind == "load" and corpus_name and len(self.registry.corpora) == 1:
2703
+ tasks = getattr(self.registry, "tasks", None) or {}
2704
+ if "load" in tasks:
2705
+ return "load"
2706
+ return None
2707
+
2708
+ @staticmethod
2709
+ def _normalize_task_kind(value: str) -> str:
2710
+ if not isinstance(value, str):
2711
+ return ""
2712
+ cleaned = value.strip().lower()
2713
+ aliases = {
2714
+ "fetch": "load",
2715
+ "sync": "load",
2716
+ "build": "index",
2717
+ "run": "query",
2718
+ }
2719
+ return aliases.get(cleaned, cleaned)
2720
+
2721
+ def _open_or_init_corpus(self, corpus_root: Path) -> "Corpus":
2722
+ from biblicus.corpus import Corpus
2723
+
2724
+ corpus = Corpus(corpus_root.resolve())
2725
+ try:
2726
+ corpus.load_catalog()
2727
+ except FileNotFoundError:
2728
+ corpus = Corpus.init(corpus_root, force=False)
2729
+ return corpus
2730
+
2731
+ def _corpus_pipeline_config(self, corpus_decl: Any) -> Optional[dict]:
2732
+ config = corpus_decl.config if isinstance(corpus_decl.config, dict) else {}
2733
+ corpus_configuration = config.get("configuration", {}) if isinstance(config, dict) else {}
2734
+ pipeline = (
2735
+ corpus_configuration.get("pipeline", {})
2736
+ if isinstance(corpus_configuration, dict)
2737
+ else {}
2738
+ )
2739
+ if not isinstance(pipeline, dict):
2740
+ return None
2741
+ if "extract" in pipeline and isinstance(pipeline.get("extract"), dict):
2742
+ return pipeline.get("extract")
2743
+ if "steps" in pipeline:
2744
+ return pipeline
2745
+ return None
2746
+
2747
+ def _retriever_index_config(self, retriever_decl: Any) -> dict:
2748
+ config = retriever_decl.config if isinstance(retriever_decl.config, dict) else {}
2749
+ configuration = config.get("configuration", {}) if isinstance(config, dict) else {}
2750
+ pipeline = configuration.get("pipeline", {}) if isinstance(configuration, dict) else {}
2751
+ if not pipeline and isinstance(config.get("pipeline"), dict):
2752
+ pipeline = config.get("pipeline") or {}
2753
+ index_config = pipeline.get("index", {}) if isinstance(pipeline, dict) else {}
2754
+ if isinstance(index_config, dict):
2755
+ return index_config
2756
+ return {}
2757
+
2758
+ def _prompt_dependency_plan(self, *, plan: Any, label: str) -> bool:
2759
+ prompt_handler = getattr(self, "dependency_prompt_handler", None)
2760
+ if callable(prompt_handler):
2761
+ return bool(prompt_handler(plan, label))
2762
+ pending = [task.kind for task in plan.tasks if task.status != "complete"]
2763
+ prompt = f"Run dependencies for {label}? ({', '.join(pending)}) [y/N]: "
2764
+ response = input(prompt).strip().lower()
2765
+ return response in {"y", "yes"}
2766
+
2767
+ def _execute_dependency_plan(
2768
+ self,
2769
+ *,
2770
+ plan: Any,
2771
+ corpus: Any,
2772
+ label: str,
2773
+ load_task_name: Optional[str] = None,
2774
+ extract_task_name: Optional[str] = None,
2775
+ index_task_name: Optional[str] = None,
2776
+ ) -> list[Any]:
2777
+ mode = self.dependency_mode
2778
+ if plan.status == "complete":
2779
+ return []
2780
+ if plan.status == "blocked":
2781
+ raise RuntimeError(plan.root.reason or f"Dependencies blocked for {label}")
2782
+ if mode == "none":
2783
+ raise RuntimeError(f"Dependencies missing for {label}")
2784
+ if mode not in {"prompt", "auto"}:
2785
+ raise RuntimeError(f"Unsupported dependency mode: {mode}")
2786
+ if mode == "prompt" and not self._prompt_dependency_plan(plan=plan, label=label):
2787
+ raise RuntimeError(f"Dependencies declined for {label}")
2788
+
2789
+ from biblicus.workflow import build_default_handler_registry
2790
+
2791
+ handler_registry = build_default_handler_registry(corpus)
2792
+ if load_task_name:
2793
+ handler_registry["load"] = lambda _: self._execute_task(load_task_name)
2794
+ if extract_task_name:
2795
+ handler_registry["extract"] = lambda _: self._execute_task(extract_task_name)
2796
+ if index_task_name:
2797
+ handler_registry["index"] = lambda _: self._execute_task(index_task_name)
2798
+ return plan.execute(mode="auto", handler_registry=handler_registry)
2799
+
2800
+ def _execute_task(self, task_name: str, *, _stack: Optional[list[str]] = None) -> Any:
2643
2801
  if not self.registry:
2644
2802
  raise RuntimeError("No registry available for task execution")
2645
2803
 
2804
+ stack = list(_stack or [])
2805
+ if task_name in stack:
2806
+ raise RuntimeError(
2807
+ f"Task dependency cycle detected: {' -> '.join(stack + [task_name])}"
2808
+ )
2809
+ stack.append(task_name)
2810
+
2646
2811
  task = self._resolve_task(task_name)
2647
2812
  if task is None:
2648
2813
  # Allow run fallback to main procedure
@@ -2650,6 +2815,10 @@ class TactusRuntime:
2650
2815
  self.task_name = None
2651
2816
  return self._execute_workflow()
2652
2817
 
2818
+ corpus_tasks = self._resolve_corpus_task_targets(task_name)
2819
+ if corpus_tasks:
2820
+ return self._execute_corpus_tasks(task_name, corpus_tasks)
2821
+
2653
2822
  retriever_tasks = self._resolve_retriever_task_targets(task_name)
2654
2823
  if retriever_tasks:
2655
2824
  return self._execute_retriever_tasks(task_name, retriever_tasks)
@@ -2657,6 +2826,10 @@ class TactusRuntime:
2657
2826
  raise RuntimeError(f"Task '{task_name}' not found")
2658
2827
 
2659
2828
  task_payload = task.model_dump()
2829
+ if task_name == "run":
2830
+ self._execute_run_dependencies()
2831
+ for dependency in self._parse_task_dependencies(task_payload):
2832
+ self._execute_task(dependency, _stack=stack)
2660
2833
  entry = task_payload.get("entry")
2661
2834
  if entry is None:
2662
2835
  if task.children:
@@ -2670,20 +2843,74 @@ class TactusRuntime:
2670
2843
  raise RuntimeError(f"Task '{task_name}' entry must be a function")
2671
2844
  return entry()
2672
2845
 
2673
- def _execute_retriever_tasks(self, task_name: str, retriever_names: list[str]) -> Any:
2674
- if not retriever_names:
2675
- raise RuntimeError(f"No retrievers available for task '{task_name}'")
2846
+ def _execute_run_dependencies(self) -> None:
2847
+ if not self.registry:
2848
+ return
2849
+ retrievers = getattr(self.registry, "retrievers", None) or {}
2850
+ if not retrievers:
2851
+ return
2852
+ for retriever_name in retrievers:
2853
+ self._ensure_retriever_dependencies(retriever_name)
2676
2854
 
2855
+ def _execute_corpus_tasks(self, task_name: str, corpus_names: list[str]) -> Any:
2856
+ if not corpus_names:
2857
+ raise RuntimeError(f"No corpora available for task '{task_name}'")
2677
2858
  base_task = task_name.split(":")[0]
2678
- if base_task == "index":
2859
+ if base_task == "extract":
2679
2860
  results = []
2680
- for retriever_name in retriever_names:
2681
- results.append(self._execute_retriever_index(retriever_name))
2861
+ for corpus_name in corpus_names:
2862
+ results.append(self._execute_corpus_extract(corpus_name))
2682
2863
  return results[0] if len(results) == 1 else results
2864
+ raise RuntimeError(f"Corpus task '{base_task}' is not supported")
2683
2865
 
2684
- raise RuntimeError(f"Retriever task '{base_task}' is not supported")
2866
+ def _execute_corpus_extract(self, corpus_name: str) -> Any:
2867
+ if not self.registry:
2868
+ raise RuntimeError("No registry available for corpus execution")
2869
+ corpus_decl = self.registry.corpora.get(corpus_name)
2870
+ if corpus_decl is None:
2871
+ raise RuntimeError(f"Corpus '{corpus_name}' not found")
2872
+ corpus_root = corpus_decl.config.get("corpus_root") or corpus_decl.config.get("root")
2873
+ if not corpus_root:
2874
+ raise RuntimeError(f"Corpus '{corpus_name}' is missing a root path")
2685
2875
 
2686
- def _execute_retriever_index(self, retriever_name: str) -> Any:
2876
+ pipeline_config = self._corpus_pipeline_config(corpus_decl)
2877
+ load_task_name = self._find_task_providing(kind="load", corpus_name=corpus_name)
2878
+ extract_task_name = self._find_task_providing(kind="extract", corpus_name=corpus_name)
2879
+
2880
+ try:
2881
+ from biblicus.workflow import build_plan_for_extract
2882
+ except Exception as exc:
2883
+ raise RuntimeError(f"Biblicus workflow unavailable: {exc}") from exc
2884
+
2885
+ corpus = self._open_or_init_corpus(Path(corpus_root))
2886
+ plan = build_plan_for_extract(
2887
+ corpus,
2888
+ pipeline_config=pipeline_config,
2889
+ load_handler_available=bool(load_task_name),
2890
+ )
2891
+ results = self._execute_dependency_plan(
2892
+ plan=plan,
2893
+ corpus=corpus,
2894
+ label=f"corpus '{corpus_name}'",
2895
+ load_task_name=load_task_name,
2896
+ extract_task_name=extract_task_name,
2897
+ )
2898
+ return results[-1] if results else {"status": "up-to-date"}
2899
+
2900
+ def _ensure_retriever_dependencies(self, retriever_name: str) -> None:
2901
+ plan, corpus, handlers = self._build_retriever_index_plan(retriever_name)
2902
+ self._execute_dependency_plan(
2903
+ plan=plan,
2904
+ corpus=corpus,
2905
+ label=f"retriever '{retriever_name}'",
2906
+ load_task_name=handlers.get("load"),
2907
+ extract_task_name=handlers.get("extract"),
2908
+ index_task_name=handlers.get("index"),
2909
+ )
2910
+
2911
+ def _build_retriever_index_plan(
2912
+ self, retriever_name: str
2913
+ ) -> tuple[Any, Any, dict[str, Optional[str]]]:
2687
2914
  if not self.registry:
2688
2915
  raise RuntimeError("No registry available for retriever execution")
2689
2916
 
@@ -2702,63 +2929,78 @@ class TactusRuntime:
2702
2929
  if not corpus_root:
2703
2930
  raise RuntimeError(f"Corpus '{retriever.corpus}' is missing a root path")
2704
2931
 
2705
- extraction_pipeline = {}
2706
- corpus_configuration = (
2707
- corpus_decl.config.get("configuration", {})
2708
- if isinstance(corpus_decl.config, dict)
2709
- else {}
2710
- )
2711
- if isinstance(corpus_configuration, dict):
2712
- pipeline = corpus_configuration.get("pipeline", {}) or {}
2713
- if isinstance(pipeline, dict):
2714
- extraction_pipeline = pipeline.get("extract", {}) or {}
2715
- if isinstance(extraction_pipeline, list):
2716
- extraction_pipeline = {}
2717
-
2718
- retriever_id = retriever.config.get("retriever_id") or retriever.config.get(
2719
- "retriever_type"
2932
+ from tactus.core.retriever_tasks import resolve_retriever_id
2933
+
2934
+ retriever_id = resolve_retriever_id(
2935
+ retriever.config if isinstance(retriever.config, dict) else {}
2720
2936
  )
2721
2937
  if not retriever_id:
2722
2938
  raise RuntimeError(
2723
- f"Retriever '{retriever_name}' is missing retriever_id; cannot build snapshot"
2939
+ f"Retriever '{retriever_name}' is missing retriever_id; cannot build plan"
2724
2940
  )
2725
2941
 
2726
- configuration = retriever.config.get("configuration", {})
2727
- pipeline = configuration.get("pipeline", {}) if isinstance(configuration, dict) else {}
2728
- if not pipeline and isinstance(retriever.config.get("pipeline"), dict):
2729
- pipeline = retriever.config.get("pipeline") or {}
2730
- index_config = pipeline.get("index", {}) if isinstance(pipeline, dict) else {}
2731
- if isinstance(index_config, list):
2732
- index_config = {}
2942
+ pipeline_config = self._corpus_pipeline_config(corpus_decl)
2943
+ index_config = self._retriever_index_config(retriever)
2944
+
2945
+ load_task_name = self._find_task_providing(kind="load", corpus_name=retriever.corpus)
2946
+ extract_task_name = self._find_task_providing(kind="extract", corpus_name=retriever.corpus)
2947
+ index_task_name = None
2948
+ for task_name, task in self._iter_task_declarations():
2949
+ for provides in self._parse_task_provides(task.model_dump()):
2950
+ if provides.get("kind") != "index":
2951
+ continue
2952
+ provided_retriever = provides.get("retriever")
2953
+ if provided_retriever in {retriever_name, retriever_id}:
2954
+ index_task_name = task_name
2955
+ break
2956
+ if index_task_name:
2957
+ break
2733
2958
 
2734
2959
  try:
2735
- from biblicus.corpus import Corpus
2736
- from biblicus.extraction import build_extraction_snapshot
2737
- from biblicus.retrievers import get_retriever
2960
+ from biblicus.workflow import build_plan_for_index
2738
2961
  except Exception as exc:
2739
- raise RuntimeError(f"Biblicus retrieval retriever unavailable: {exc}") from exc
2740
-
2741
- corpus = Corpus(Path(corpus_root))
2742
- retriever_impl = get_retriever(retriever_id)
2743
- configuration_name = retriever_name
2744
-
2745
- if extraction_pipeline and isinstance(extraction_pipeline, dict):
2746
- extraction_manifest = build_extraction_snapshot(
2747
- corpus,
2748
- extractor_id="pipeline",
2749
- configuration_name=f"{retriever.corpus or retriever_name}-extract",
2750
- configuration=extraction_pipeline,
2751
- )
2752
- if isinstance(index_config, dict) and "extraction_snapshot" not in index_config:
2753
- index_config["extraction_snapshot"] = (
2754
- f"{extraction_manifest.configuration.extractor_id}:"
2755
- f"{extraction_manifest.snapshot_id}"
2756
- )
2757
- snapshot = retriever_impl.build_snapshot(
2758
- corpus, configuration_name=configuration_name, configuration=index_config
2962
+ raise RuntimeError(f"Biblicus workflow unavailable: {exc}") from exc
2963
+
2964
+ corpus = self._open_or_init_corpus(Path(corpus_root))
2965
+ plan = build_plan_for_index(
2966
+ corpus,
2967
+ retriever_id,
2968
+ pipeline_config=pipeline_config,
2969
+ index_config=index_config,
2970
+ load_handler_available=bool(load_task_name),
2759
2971
  )
2760
2972
 
2761
- return snapshot.model_dump()
2973
+ handlers = {
2974
+ "load": load_task_name,
2975
+ "extract": extract_task_name,
2976
+ "index": index_task_name,
2977
+ }
2978
+ return plan, corpus, handlers
2979
+
2980
+ def _execute_retriever_tasks(self, task_name: str, retriever_names: list[str]) -> Any:
2981
+ if not retriever_names:
2982
+ raise RuntimeError(f"No retrievers available for task '{task_name}'")
2983
+
2984
+ base_task = task_name.split(":")[0]
2985
+ if base_task == "index":
2986
+ results = []
2987
+ for retriever_name in retriever_names:
2988
+ results.append(self._execute_retriever_index(retriever_name))
2989
+ return results[0] if len(results) == 1 else results
2990
+
2991
+ raise RuntimeError(f"Retriever task '{base_task}' is not supported")
2992
+
2993
+ def _execute_retriever_index(self, retriever_name: str) -> Any:
2994
+ plan, corpus, handlers = self._build_retriever_index_plan(retriever_name)
2995
+ results = self._execute_dependency_plan(
2996
+ plan=plan,
2997
+ corpus=corpus,
2998
+ label=f"retriever '{retriever_name}'",
2999
+ load_task_name=handlers.get("load"),
3000
+ extract_task_name=handlers.get("extract"),
3001
+ index_task_name=handlers.get("index"),
3002
+ )
3003
+ return results[-1] if results else {"status": "up-to-date"}
2762
3004
 
2763
3005
  def _resolve_task(self, task_name: str) -> Optional[TaskDeclaration]:
2764
3006
  if not self.registry:
@@ -2806,6 +3048,21 @@ class TactusRuntime:
2806
3048
  return [name for name in targets if name == target]
2807
3049
  return targets
2808
3050
 
3051
+ def _resolve_corpus_task_targets(self, task_name: str) -> list[str]:
3052
+ if not self.registry or not self.registry.corpora:
3053
+ return []
3054
+ segments = [segment for segment in task_name.split(":") if segment]
3055
+ if not segments:
3056
+ return []
3057
+ task = segments[0]
3058
+ target = segments[1] if len(segments) > 1 else None
3059
+ if task != "extract":
3060
+ return []
3061
+ targets = list(self.registry.corpora.keys())
3062
+ if target:
3063
+ return [name for name in targets if name == target]
3064
+ return targets
3065
+
2809
3066
  def _expand_inline_task_children(self, registry: ProcedureRegistry) -> None:
2810
3067
  if not registry or not registry.tasks:
2811
3068
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tactus
3
- Version: 0.39.0
3
+ Version: 0.40.0
4
4
  Summary: Tactus: Lua-based DSL for agentic workflows
5
5
  Project-URL: Homepage, https://github.com/AnthusAI/Tactus
6
6
  Project-URL: Documentation, https://github.com/AnthusAI/Tactus/tree/main/docs
@@ -1,4 +1,4 @@
1
- tactus/__init__.py,sha256=FbndoB7McjD-smRU9tGDLvNIw7o7-dCqiKRP3LSD23c,1245
1
+ tactus/__init__.py,sha256=kZIgMAqEdibTGcY5NARvHI3BnbR8fsjBkiGdbDZ2JBg,1245
2
2
  tactus/adapters/__init__.py,sha256=47Y8kGBR4QGxqEGvjA1mneOSACb2L7oELnj6P2uI7uk,759
3
3
  tactus/adapters/broker_log.py,sha256=9ZR-rJdyW6bMNZx3OfXoQEnDxcAzNsiJ8aPxZGqJYrM,6019
4
4
  tactus/adapters/cli_hitl.py,sha256=Nrfoi35Ei9fTMReLG2QxKkhKyIvl3pYcAUdQCAUOZDk,17361
@@ -29,7 +29,7 @@ tactus/broker/protocol.py,sha256=v4DFSVoecerqxbqK-vbRfYEAD10tk-QXNH_d9PFgkWg,534
29
29
  tactus/broker/server.py,sha256=s0_Uokovf5s-IR8Ieb3r1h9dnt4eO_PT0aycwuHwhks,56236
30
30
  tactus/broker/stdio.py,sha256=JXkEz-PCU3IQXNkt16YJtYmwkR43eS6CfjxAHc-YCfQ,439
31
31
  tactus/cli/__init__.py,sha256=kVhdCkwWEPdt3vn9si-iKvh6M9817aOH6rLSsNzRuyg,80
32
- tactus/cli/app.py,sha256=-w6iY0rjbTn3TCV9vTIrUp7wV2WdHmeWa16ZgD7yEC4,102181
32
+ tactus/cli/app.py,sha256=I_89_mis8JFnfJ6yLNFA_PmQrmd79Ci3XZ0Pc0-6MDU,103242
33
33
  tactus/cli/control.py,sha256=jCKKy8f6x8wV5im-MwxOtgz85oYLTHhPKXx-3FtRwoU,13364
34
34
  tactus/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  tactus/core/__init__.py,sha256=TK5rWr3HmOO_igFa5ESGp6teWwS58vnvQhIWqkcgqwk,880
@@ -37,7 +37,7 @@ tactus/core/compaction.py,sha256=mVwFsEb9FEc-bMPFcKgXyAO-pVnAXeQGZka7RWtjVsY,397
37
37
  tactus/core/config_manager.py,sha256=kxz853j4Nx97SBh8-fAar_OfmfWZvvcfafLyxjTQG1A,35131
38
38
  tactus/core/context_assembler.py,sha256=tbd-XACBvAFkBlPrzAAZ_L-2JGnO4meS-GL4ilE7XHw,2406
39
39
  tactus/core/context_models.py,sha256=YHKjHKEzLk3fgpkmXYQ9RWDPKRNStt92ve40xcMv3p0,975
40
- tactus/core/dsl_stubs.py,sha256=l1icTqTM092Z2AsrdLAo3WjZtDDVggMBG662KdWUIIU,110257
40
+ tactus/core/dsl_stubs.py,sha256=-bTopHqUq6B_CSsnVkeNK7uy-WbJD8n3B4LOCpXGcXM,110492
41
41
  tactus/core/exceptions.py,sha256=r-4IrZw_WrioBkpMR42Q3LNwEqimJ6jxXgfOo2wANTM,1962
42
42
  tactus/core/execution_context.py,sha256=OgTe9E0xc3nTQbCTEaaBfI11dVA1-J0eFz0eQR4XMZY,29402
43
43
  tactus/core/lua_sandbox.py,sha256=Ln2P1gdxVl396HLvEw7FmDKV3eVdVdbDzYHMbDSEciY,19106
@@ -47,7 +47,7 @@ tactus/core/output_validator.py,sha256=LcSjgAiDRvzsj2uWasQihengQRt7R-ZYaPiLQPbZy
47
47
  tactus/core/registry.py,sha256=z5HFoi1zb81-TJ_6qeTWSte4J0Zw-NYmN19VjjBjJE8,26577
48
48
  tactus/core/retrieval.py,sha256=AMDa4X4YWaSDNdf3T3hRUzgM4W1l_sGqxAX5Jki5PH0,12083
49
49
  tactus/core/retriever_tasks.py,sha256=i2wsoZN9cZOypkdPAJKqlrRP_jwtqpHVCFoxPL0Iirk,931
50
- tactus/core/runtime.py,sha256=YMZKfUj2sfyGyBFdKvXWjbt2dbf-mckz4CefeMm_7sw,157790
50
+ tactus/core/runtime.py,sha256=_egKpvp1qSB0q1zTdcjXOeaLZIEB2oQWoiFxih9K_yw,168471
51
51
  tactus/core/template_resolver.py,sha256=r97KzFNaK4nFSoWtIFZeSKyuUWgbp-ay1_BGrb-BgUY,4179
52
52
  tactus/core/yaml_parser.py,sha256=JD7Nehaxw3uP1KV_uTU_xiXTbEWqoKOceU5tAJ4lcH8,13985
53
53
  tactus/core/dependencies/__init__.py,sha256=28-TM7_i-JqTD3hvkq1kMzr__A8VjfIKXymdW9mn5NM,362
@@ -226,8 +226,8 @@ tactus/validation/generated/LuaParserVisitor.py,sha256=ageKSmHPxnO3jBS2fBtkmYBOd
226
226
  tactus/validation/generated/__init__.py,sha256=5gWlwRI0UvmHw2fnBpj_IG6N8oZeabr5tbj1AODDvjc,196
227
227
  tactus/validation/grammar/LuaLexer.g4,sha256=t2MXiTCr127RWAyQGvamkcU_m4veqPzSuHUtAKwalw4,2771
228
228
  tactus/validation/grammar/LuaParser.g4,sha256=ceZenb90BdiZmVdOxMGj9qJk3QbbWVZe5HUqPgoePfY,3202
229
- tactus-0.39.0.dist-info/METADATA,sha256=5Axn0cmSvHUPhqcpvlbMouG27Gu2YkmxHfINh3ucnig,60383
230
- tactus-0.39.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
231
- tactus-0.39.0.dist-info/entry_points.txt,sha256=vWseqty8m3z-Worje0IYxlioMjPDCoSsm0AtY4GghBY,47
232
- tactus-0.39.0.dist-info/licenses/LICENSE,sha256=ivohBcAIYnaLPQ-lKEeCXSMvQUVISpQfKyxHBHoa4GA,1066
233
- tactus-0.39.0.dist-info/RECORD,,
229
+ tactus-0.40.0.dist-info/METADATA,sha256=ZMyLh83LARAXlKw67who3Yfbgkyw5_4ZCJ0PAO3gSTI,60383
230
+ tactus-0.40.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
231
+ tactus-0.40.0.dist-info/entry_points.txt,sha256=vWseqty8m3z-Worje0IYxlioMjPDCoSsm0AtY4GghBY,47
232
+ tactus-0.40.0.dist-info/licenses/LICENSE,sha256=ivohBcAIYnaLPQ-lKEeCXSMvQUVISpQfKyxHBHoa4GA,1066
233
+ tactus-0.40.0.dist-info/RECORD,,