jarviscore-framework 0.1.0__py3-none-any.whl → 0.2.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.
Files changed (99) hide show
  1. examples/autoagent_distributed_example.py +211 -0
  2. examples/custom_profile_decorator.py +134 -0
  3. examples/custom_profile_wrap.py +168 -0
  4. examples/customagent_distributed_example.py +362 -0
  5. examples/customagent_p2p_example.py +347 -0
  6. jarviscore/__init__.py +60 -15
  7. jarviscore/adapter/__init__.py +40 -0
  8. jarviscore/adapter/decorator.py +336 -0
  9. jarviscore/adapter/wrapper.py +303 -0
  10. jarviscore/cli/check.py +18 -13
  11. jarviscore/cli/scaffold.py +178 -0
  12. jarviscore/cli/smoketest.py +3 -2
  13. jarviscore/context/__init__.py +40 -0
  14. jarviscore/context/dependency.py +160 -0
  15. jarviscore/context/jarvis_context.py +207 -0
  16. jarviscore/context/memory.py +155 -0
  17. jarviscore/core/agent.py +44 -1
  18. jarviscore/core/mesh.py +196 -35
  19. jarviscore/data/.env.example +146 -0
  20. jarviscore/data/__init__.py +7 -0
  21. jarviscore/data/examples/autoagent_distributed_example.py +211 -0
  22. jarviscore/data/examples/calculator_agent_example.py +77 -0
  23. jarviscore/data/examples/customagent_distributed_example.py +362 -0
  24. jarviscore/data/examples/customagent_p2p_example.py +347 -0
  25. jarviscore/data/examples/multi_agent_workflow.py +132 -0
  26. jarviscore/data/examples/research_agent_example.py +76 -0
  27. jarviscore/docs/API_REFERENCE.md +264 -51
  28. jarviscore/docs/AUTOAGENT_GUIDE.md +198 -0
  29. jarviscore/docs/CONFIGURATION.md +41 -23
  30. jarviscore/docs/CUSTOMAGENT_GUIDE.md +415 -0
  31. jarviscore/docs/GETTING_STARTED.md +113 -17
  32. jarviscore/docs/TROUBLESHOOTING.md +155 -13
  33. jarviscore/docs/USER_GUIDE.md +144 -363
  34. jarviscore/execution/llm.py +23 -16
  35. jarviscore/orchestration/engine.py +20 -8
  36. jarviscore/p2p/__init__.py +10 -0
  37. jarviscore/p2p/coordinator.py +129 -0
  38. jarviscore/p2p/messages.py +87 -0
  39. jarviscore/p2p/peer_client.py +576 -0
  40. jarviscore/p2p/peer_tool.py +268 -0
  41. jarviscore_framework-0.2.0.dist-info/METADATA +143 -0
  42. jarviscore_framework-0.2.0.dist-info/RECORD +132 -0
  43. {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.2.0.dist-info}/WHEEL +1 -1
  44. {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.2.0.dist-info}/top_level.txt +1 -0
  45. test_logs/code_registry/functions/data_generator-558779ed_560ebc37.py +7 -0
  46. test_logs/code_registry/functions/data_generator-5ed3609e_560ebc37.py +7 -0
  47. test_logs/code_registry/functions/data_generator-66da0356_43970bb9.py +25 -0
  48. test_logs/code_registry/functions/data_generator-7a2fac83_583709d9.py +36 -0
  49. test_logs/code_registry/functions/data_generator-888b670f_aa235863.py +9 -0
  50. test_logs/code_registry/functions/data_generator-9ca5f642_aa235863.py +9 -0
  51. test_logs/code_registry/functions/data_generator-bfd90775_560ebc37.py +7 -0
  52. test_logs/code_registry/functions/data_generator-e95d2f7d_aa235863.py +9 -0
  53. test_logs/code_registry/functions/data_generator-f60ca8a2_327eb8c2.py +29 -0
  54. test_logs/code_registry/functions/mathematician-02adf9ee_958658d9.py +19 -0
  55. test_logs/code_registry/functions/mathematician-0706fb57_5df13441.py +23 -0
  56. test_logs/code_registry/functions/mathematician-153c9c4a_ba59c918.py +83 -0
  57. test_logs/code_registry/functions/mathematician-287e61c0_41daa793.py +18 -0
  58. test_logs/code_registry/functions/mathematician-2967af5a_863c2cc6.py +17 -0
  59. test_logs/code_registry/functions/mathematician-303ca6d6_5df13441.py +23 -0
  60. test_logs/code_registry/functions/mathematician-308a4afd_cbf5064d.py +73 -0
  61. test_logs/code_registry/functions/mathematician-353f16e2_0968bcf5.py +18 -0
  62. test_logs/code_registry/functions/mathematician-3c22475a_41daa793.py +17 -0
  63. test_logs/code_registry/functions/mathematician-5bac1029_0968bcf5.py +18 -0
  64. test_logs/code_registry/functions/mathematician-640f76b2_9198780b.py +19 -0
  65. test_logs/code_registry/functions/mathematician-752fa7ea_863c2cc6.py +17 -0
  66. test_logs/code_registry/functions/mathematician-baf9ef39_0968bcf5.py +18 -0
  67. test_logs/code_registry/functions/mathematician-bc8b2a2f_5df13441.py +23 -0
  68. test_logs/code_registry/functions/mathematician-c31e4686_41daa793.py +18 -0
  69. test_logs/code_registry/functions/mathematician-cc84c84c_863c2cc6.py +17 -0
  70. test_logs/code_registry/functions/mathematician-dd7c7144_9198780b.py +19 -0
  71. test_logs/code_registry/functions/mathematician-e671c256_41ea4487.py +74 -0
  72. test_logs/code_registry/functions/report_generator-1a878fcc_18d44bdc.py +47 -0
  73. test_logs/code_registry/functions/report_generator-25c1c331_cea57d0d.py +35 -0
  74. test_logs/code_registry/functions/report_generator-37552117_e711c2b9.py +35 -0
  75. test_logs/code_registry/functions/report_generator-bc662768_e711c2b9.py +35 -0
  76. test_logs/code_registry/functions/report_generator-d6c0e76b_5e7722ec.py +44 -0
  77. test_logs/code_registry/functions/report_generator-f270fb02_680529c3.py +44 -0
  78. test_logs/code_registry/functions/text_processor-11393b14_4370d3ed.py +40 -0
  79. test_logs/code_registry/functions/text_processor-7d02dfc3_d3b569be.py +37 -0
  80. test_logs/code_registry/functions/text_processor-8adb5e32_9168c5fe.py +13 -0
  81. test_logs/code_registry/functions/text_processor-c58ffc19_78b4ceac.py +42 -0
  82. test_logs/code_registry/functions/text_processor-cd5977b1_9168c5fe.py +13 -0
  83. test_logs/code_registry/functions/text_processor-ec1c8773_9168c5fe.py +13 -0
  84. tests/test_01_analyst_standalone.py +124 -0
  85. tests/test_02_assistant_standalone.py +164 -0
  86. tests/test_03_analyst_with_framework.py +945 -0
  87. tests/test_04_assistant_with_framework.py +1002 -0
  88. tests/test_05_integration.py +1301 -0
  89. tests/test_06_real_llm_integration.py +760 -0
  90. tests/test_07_distributed_single_node.py +578 -0
  91. tests/test_08_distributed_multi_node.py +454 -0
  92. tests/test_09_distributed_autoagent.py +509 -0
  93. tests/test_10_distributed_customagent.py +787 -0
  94. tests/test_context.py +467 -0
  95. tests/test_decorator.py +622 -0
  96. tests/test_mesh.py +35 -4
  97. jarviscore_framework-0.1.0.dist-info/METADATA +0 -136
  98. jarviscore_framework-0.1.0.dist-info/RECORD +0 -55
  99. {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,178 @@
1
+ """
2
+ JarvisCore Project Initialization CLI
3
+
4
+ Scaffolds a new JarvisCore project with configuration and examples.
5
+
6
+ Usage:
7
+ python -m jarviscore.cli.scaffold # Create .env from template
8
+ python -m jarviscore.cli.scaffold --examples # Also copy example files
9
+ python -m jarviscore.cli.scaffold --force # Overwrite existing files
10
+ """
11
+
12
+ import sys
13
+ import shutil
14
+ from pathlib import Path
15
+ from importlib import resources
16
+ import argparse
17
+
18
+
19
+ def get_data_path() -> Path:
20
+ """Get path to the data directory within the package."""
21
+ # Python 3.9+ approach using importlib.resources
22
+ try:
23
+ # resources.files() returns a Traversable, convert to Path
24
+ data_path = resources.files('jarviscore.data')
25
+ return Path(str(data_path))
26
+ except (TypeError, AttributeError):
27
+ # Fallback for older Python or if resources.files doesn't work
28
+ import jarviscore.data
29
+ return Path(jarviscore.data.__file__).parent
30
+
31
+
32
+ def copy_env_example(dest_dir: Path, force: bool = False) -> bool:
33
+ """
34
+ Copy .env.example to destination directory.
35
+
36
+ Args:
37
+ dest_dir: Destination directory
38
+ force: Overwrite if exists
39
+
40
+ Returns:
41
+ True if copied, False if skipped
42
+ """
43
+ data_path = get_data_path()
44
+ src = data_path / '.env.example'
45
+ dest = dest_dir / '.env.example'
46
+
47
+ if not src.exists():
48
+ print(f"✗ Source file not found: {src}")
49
+ return False
50
+
51
+ if dest.exists() and not force:
52
+ print(f"⚠ {dest.name} already exists (use --force to overwrite)")
53
+ return False
54
+
55
+ shutil.copy2(src, dest)
56
+ print(f"✓ Created {dest.name}")
57
+ return True
58
+
59
+
60
+ def copy_examples(dest_dir: Path, force: bool = False) -> bool:
61
+ """
62
+ Copy example files to destination directory.
63
+
64
+ Args:
65
+ dest_dir: Destination directory
66
+ force: Overwrite if exists
67
+
68
+ Returns:
69
+ True if copied, False if skipped
70
+ """
71
+ data_path = get_data_path()
72
+ src = data_path / 'examples'
73
+ dest = dest_dir / 'examples'
74
+
75
+ if not src.exists():
76
+ print(f"✗ Examples directory not found: {src}")
77
+ return False
78
+
79
+ if dest.exists() and not force:
80
+ print(f"⚠ examples/ directory already exists (use --force to overwrite)")
81
+ return False
82
+
83
+ if dest.exists() and force:
84
+ shutil.rmtree(dest)
85
+
86
+ shutil.copytree(src, dest)
87
+
88
+ # Count files copied
89
+ file_count = sum(1 for _ in dest.glob('*.py'))
90
+ print(f"✓ Created examples/ directory ({file_count} files)")
91
+ return True
92
+
93
+
94
+ def print_header():
95
+ """Print initialization header."""
96
+ print("\n" + "=" * 60)
97
+ print(" JarvisCore Project Initialization")
98
+ print("=" * 60 + "\n")
99
+
100
+
101
+ def print_next_steps(env_created: bool, examples_created: bool):
102
+ """Print next steps after initialization."""
103
+ print("\n" + "=" * 60)
104
+ print(" Next Steps")
105
+ print("=" * 60)
106
+
107
+ steps = []
108
+
109
+ if env_created:
110
+ steps.append("1. Copy and configure your environment:\n"
111
+ " cp .env.example .env\n"
112
+ " # Edit .env and add your LLM API key")
113
+
114
+ steps.append(f"{'2' if env_created else '1'}. Validate your setup:\n"
115
+ " python -m jarviscore.cli.check --validate-llm")
116
+
117
+ steps.append(f"{'3' if env_created else '2'}. Run smoke test:\n"
118
+ " python -m jarviscore.cli.smoketest")
119
+
120
+ if examples_created:
121
+ steps.append(f"{'4' if env_created else '3'}. Try an example:\n"
122
+ " python examples/calculator_agent_example.py for AutoAgent Profile or python examples/customagent_p2p_example.py ")
123
+
124
+ for step in steps:
125
+ print(f"\n{step}")
126
+
127
+ print()
128
+
129
+
130
+ def main():
131
+ """CLI entry point."""
132
+ parser = argparse.ArgumentParser(
133
+ description='Initialize a new JarvisCore project'
134
+ )
135
+ parser.add_argument(
136
+ '--examples',
137
+ action='store_true',
138
+ help='Also copy example agent files'
139
+ )
140
+ parser.add_argument(
141
+ '--force',
142
+ action='store_true',
143
+ help='Overwrite existing files'
144
+ )
145
+ parser.add_argument(
146
+ '--dir',
147
+ type=str,
148
+ default='.',
149
+ help='Target directory (default: current directory)'
150
+ )
151
+
152
+ args = parser.parse_args()
153
+ dest_dir = Path(args.dir).resolve()
154
+
155
+ print_header()
156
+ print(f"Initializing in: {dest_dir}\n")
157
+
158
+ # Ensure destination exists
159
+ dest_dir.mkdir(parents=True, exist_ok=True)
160
+
161
+ # Copy files
162
+ env_created = copy_env_example(dest_dir, args.force)
163
+
164
+ examples_created = False
165
+ if args.examples:
166
+ examples_created = copy_examples(dest_dir, args.force)
167
+
168
+ # Summary
169
+ if env_created or examples_created:
170
+ print_next_steps(env_created, examples_created)
171
+ sys.exit(0)
172
+ else:
173
+ print("\n⚠ No files were created. Use --force to overwrite existing files.")
174
+ sys.exit(1)
175
+
176
+
177
+ if __name__ == '__main__':
178
+ main()
@@ -311,8 +311,9 @@ class SmokeTest:
311
311
 
312
312
  print("\n✓ All smoke tests passed!")
313
313
  print("\nJarvisCore is working correctly. Next steps:")
314
- print(" 1. Try examples: python examples/calculator_agent_example.py")
315
- print(" 2. Read user guide: docs/USER_GUIDE.md")
314
+ print(" 1. Try examples - AutoAgent Profile: python examples/calculator_agent_example.py")
315
+ print(" 2. Try examples - CustomAgent Profile: python examples/customagent_p2p_example.py")
316
+ print(" 3. Read user guide: docs/USER_GUIDE.md")
316
317
  print(" 3. Build your first agent: docs/GETTING_STARTED.md")
317
318
  print()
318
319
  return True
@@ -0,0 +1,40 @@
1
+ """
2
+ Context module for JarvisCore Custom Profile.
3
+
4
+ Provides orchestration primitives for wrapped agents:
5
+ - JarvisContext: Unified context with workflow info and accessors
6
+ - MemoryAccessor: Clean API over workflow memory
7
+ - DependencyAccessor: Clean API over dependency management
8
+
9
+ These are facades over existing JarvisCore components, providing
10
+ a developer-friendly interface for Custom Profile agents.
11
+
12
+ Example:
13
+ from jarviscore.context import JarvisContext
14
+
15
+ @jarvis_agent(role="processor", capabilities=["processing"])
16
+ class Processor:
17
+ def run(self, task, ctx: JarvisContext):
18
+ # Access previous step
19
+ data = ctx.previous("step1")
20
+
21
+ # Access memory
22
+ all_data = ctx.memory.all()
23
+
24
+ # Check dependencies
25
+ if ctx.deps.is_ready("optional"):
26
+ optional = ctx.previous("optional")
27
+
28
+ return {"processed": process(data)}
29
+ """
30
+
31
+ from .jarvis_context import JarvisContext, create_context
32
+ from .memory import MemoryAccessor
33
+ from .dependency import DependencyAccessor
34
+
35
+ __all__ = [
36
+ 'JarvisContext',
37
+ 'create_context',
38
+ 'MemoryAccessor',
39
+ 'DependencyAccessor',
40
+ ]
@@ -0,0 +1,160 @@
1
+ """
2
+ DependencyAccessor - Clean API over DependencyManager.
3
+
4
+ Wraps the existing orchestration.DependencyManager to provide
5
+ a developer-friendly interface for Custom Profile agents.
6
+ """
7
+ from typing import List, Dict, Any, Tuple, Optional
8
+
9
+
10
+ class DependencyAccessor:
11
+ """
12
+ Provides clean access to dependency management.
13
+
14
+ This is a facade over the existing DependencyManager class.
15
+ It provides a simpler interface for checking and waiting on dependencies.
16
+
17
+ Example:
18
+ # In agent's run method with ctx: JarvisContext
19
+ await ctx.deps.wait_for(["step1", "step2"])
20
+ ready, missing = ctx.deps.check(["step1", "step2"])
21
+ if ctx.deps.is_ready("optional_step"):
22
+ ...
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ dependency_manager: Optional[Any],
28
+ memory: Dict[str, Any]
29
+ ):
30
+ """
31
+ Initialize dependency accessor.
32
+
33
+ Args:
34
+ dependency_manager: Reference to orchestration.DependencyManager
35
+ memory: Reference to WorkflowEngine.memory dict
36
+ """
37
+ self._manager = dependency_manager
38
+ self._memory = memory
39
+
40
+ async def wait_for(
41
+ self,
42
+ step_ids: List[str],
43
+ timeout: float = 300.0
44
+ ) -> Dict[str, Any]:
45
+ """
46
+ Wait for specific steps to complete.
47
+
48
+ Blocks until all specified steps have outputs in memory.
49
+
50
+ Args:
51
+ step_ids: List of step IDs to wait for
52
+ timeout: Maximum wait time in seconds (default: 5 minutes)
53
+
54
+ Returns:
55
+ Dictionary of step_id -> output
56
+
57
+ Raises:
58
+ TimeoutError: If dependencies not ready within timeout
59
+
60
+ Example:
61
+ results = await deps.wait_for(["step1", "step2"])
62
+ step1_data = results["step1"]
63
+ """
64
+ if self._manager is None:
65
+ # Fallback: simple check without manager
66
+ return self._simple_wait(step_ids)
67
+
68
+ return await self._manager.wait_for(step_ids, self._memory, timeout)
69
+
70
+ def _simple_wait(self, step_ids: List[str]) -> Dict[str, Any]:
71
+ """
72
+ Simple synchronous check (used when manager not available).
73
+
74
+ Returns outputs for steps that exist in memory.
75
+ """
76
+ result = {}
77
+ for step_id in step_ids:
78
+ if step_id in self._memory:
79
+ value = self._memory[step_id]
80
+ if isinstance(value, dict) and 'output' in value:
81
+ result[step_id] = value['output']
82
+ else:
83
+ result[step_id] = value
84
+ return result
85
+
86
+ def check(self, step_ids: List[str]) -> Tuple[bool, List[str]]:
87
+ """
88
+ Check if dependencies are satisfied (non-blocking).
89
+
90
+ Args:
91
+ step_ids: Steps to check
92
+
93
+ Returns:
94
+ Tuple of (all_satisfied, missing_step_ids)
95
+
96
+ Example:
97
+ ready, missing = deps.check(["step1", "step2"])
98
+ if not ready:
99
+ print(f"Still waiting for: {missing}")
100
+ """
101
+ if self._manager is None:
102
+ # Fallback: simple check without manager
103
+ missing = [s for s in step_ids if s not in self._memory]
104
+ return (len(missing) == 0, missing)
105
+
106
+ return self._manager.check_dependencies(step_ids, self._memory)
107
+
108
+ def is_ready(self, step_id: str) -> bool:
109
+ """
110
+ Check if a single step is ready (non-blocking).
111
+
112
+ Args:
113
+ step_id: Step to check
114
+
115
+ Returns:
116
+ True if step output exists in memory
117
+
118
+ Example:
119
+ if deps.is_ready("optional_step"):
120
+ data = ctx.memory.get("optional_step")
121
+ """
122
+ return step_id in self._memory
123
+
124
+ def all_ready(self, step_ids: List[str]) -> bool:
125
+ """
126
+ Check if all specified steps are ready.
127
+
128
+ Args:
129
+ step_ids: Steps to check
130
+
131
+ Returns:
132
+ True if all steps have outputs in memory
133
+
134
+ Example:
135
+ if deps.all_ready(["step1", "step2", "step3"]):
136
+ # All dependencies satisfied
137
+ ...
138
+ """
139
+ return all(self.is_ready(s) for s in step_ids)
140
+
141
+ def any_ready(self, step_ids: List[str]) -> bool:
142
+ """
143
+ Check if any of the specified steps are ready.
144
+
145
+ Args:
146
+ step_ids: Steps to check
147
+
148
+ Returns:
149
+ True if at least one step has output in memory
150
+
151
+ Example:
152
+ if deps.any_ready(["cache_step", "compute_step"]):
153
+ # At least one source available
154
+ ...
155
+ """
156
+ return any(self.is_ready(s) for s in step_ids)
157
+
158
+ def __repr__(self) -> str:
159
+ ready_count = sum(1 for k in self._memory.keys())
160
+ return f"<DependencyAccessor ready_steps={ready_count}>"
@@ -0,0 +1,207 @@
1
+ """
2
+ JarvisContext - Unified context for Custom Profile agents.
3
+
4
+ Provides a single object that gives agents access to:
5
+ - Workflow information (workflow_id, step_id)
6
+ - Task information (task description, params)
7
+ - Memory (shared state between steps)
8
+ - Dependencies (check/wait for other steps)
9
+
10
+ This is a facade over existing JarvisCore primitives.
11
+ """
12
+ from dataclasses import dataclass, field
13
+ from typing import Dict, Any, Optional, List
14
+
15
+ from .memory import MemoryAccessor
16
+ from .dependency import DependencyAccessor
17
+
18
+
19
+ @dataclass
20
+ class JarvisContext:
21
+ """
22
+ Context passed to wrapped agents during execution.
23
+
24
+ Provides unified access to JarvisCore orchestration primitives.
25
+ Agents receive this as the `ctx` parameter when they declare it
26
+ in their run method signature.
27
+
28
+ Attributes:
29
+ workflow_id: Unique identifier for the current workflow
30
+ step_id: Unique identifier for the current step
31
+ task: Task description string
32
+ params: Task parameters dictionary
33
+ memory: Accessor for shared workflow memory
34
+ deps: Accessor for dependency management
35
+
36
+ Example:
37
+ @jarvis_agent(role="aggregator", capabilities=["aggregation"])
38
+ class Aggregator:
39
+ def run(self, task, ctx: JarvisContext):
40
+ # Access previous step output
41
+ step1_data = ctx.previous("step1")
42
+
43
+ # Access all previous results
44
+ all_results = ctx.memory.all()
45
+
46
+ # Check workflow info
47
+ print(f"Running step {ctx.step_id} in {ctx.workflow_id}")
48
+
49
+ # Use params
50
+ threshold = ctx.params.get("threshold", 0.5)
51
+
52
+ return {"aggregated": process(step1_data)}
53
+ """
54
+
55
+ # Workflow info
56
+ workflow_id: str
57
+ step_id: str
58
+
59
+ # Task info
60
+ task: str
61
+ params: Dict[str, Any] = field(default_factory=dict)
62
+
63
+ # Orchestration accessors
64
+ memory: MemoryAccessor = None
65
+ deps: DependencyAccessor = None
66
+
67
+ def previous(self, step_id: str, default: Any = None) -> Any:
68
+ """
69
+ Get output from a previous step.
70
+
71
+ Convenience method that delegates to memory.get().
72
+
73
+ Args:
74
+ step_id: ID of the step to get output from
75
+ default: Default value if not found
76
+
77
+ Returns:
78
+ Step output or default
79
+
80
+ Example:
81
+ step1_result = ctx.previous("step1")
82
+ optional = ctx.previous("optional_step", default={})
83
+ """
84
+ if self.memory is None:
85
+ return default
86
+ return self.memory.get(step_id, default)
87
+
88
+ def all_previous(self) -> Dict[str, Any]:
89
+ """
90
+ Get all previous step outputs.
91
+
92
+ Convenience method that delegates to memory.all().
93
+
94
+ Returns:
95
+ Dictionary of step_id -> output
96
+
97
+ Example:
98
+ all_results = ctx.all_previous()
99
+ for step_id, output in all_results.items():
100
+ print(f"{step_id} produced: {output}")
101
+ """
102
+ if self.memory is None:
103
+ return {}
104
+ return self.memory.all()
105
+
106
+ @property
107
+ def previous_results(self) -> Dict[str, Any]:
108
+ """
109
+ Alias for all_previous().
110
+
111
+ Provides property-style access to all previous results.
112
+
113
+ Example:
114
+ results = ctx.previous_results
115
+ """
116
+ return self.all_previous()
117
+
118
+ def has_previous(self, step_id: str) -> bool:
119
+ """
120
+ Check if a previous step's output exists.
121
+
122
+ Args:
123
+ step_id: Step to check
124
+
125
+ Returns:
126
+ True if output exists
127
+
128
+ Example:
129
+ if ctx.has_previous("optional_step"):
130
+ data = ctx.previous("optional_step")
131
+ """
132
+ if self.memory is None:
133
+ return False
134
+ return self.memory.has(step_id)
135
+
136
+ def get_param(self, key: str, default: Any = None) -> Any:
137
+ """
138
+ Get a task parameter by key.
139
+
140
+ Args:
141
+ key: Parameter key
142
+ default: Default value if not found
143
+
144
+ Returns:
145
+ Parameter value or default
146
+
147
+ Example:
148
+ threshold = ctx.get_param("threshold", 0.5)
149
+ mode = ctx.get_param("mode", "default")
150
+ """
151
+ return self.params.get(key, default)
152
+
153
+ def __repr__(self) -> str:
154
+ return (
155
+ f"<JarvisContext "
156
+ f"workflow={self.workflow_id} "
157
+ f"step={self.step_id} "
158
+ f"params={list(self.params.keys())}>"
159
+ )
160
+
161
+
162
+ def create_context(
163
+ workflow_id: str,
164
+ step_id: str,
165
+ task: str,
166
+ params: Dict[str, Any],
167
+ memory_dict: Dict[str, Any],
168
+ dependency_manager: Optional[Any] = None
169
+ ) -> JarvisContext:
170
+ """
171
+ Factory function to create a JarvisContext.
172
+
173
+ Used internally by the decorator and WorkflowEngine to create
174
+ context objects for agents.
175
+
176
+ Args:
177
+ workflow_id: Workflow identifier
178
+ step_id: Step identifier
179
+ task: Task description
180
+ params: Task parameters
181
+ memory_dict: Reference to WorkflowEngine.memory
182
+ dependency_manager: Optional reference to DependencyManager
183
+
184
+ Returns:
185
+ Configured JarvisContext instance
186
+
187
+ Example:
188
+ ctx = create_context(
189
+ workflow_id="pipeline-1",
190
+ step_id="step2",
191
+ task="Process data",
192
+ params={"threshold": 0.5},
193
+ memory_dict=engine.memory,
194
+ dependency_manager=engine.dependency_manager
195
+ )
196
+ """
197
+ memory_accessor = MemoryAccessor(memory_dict, step_id)
198
+ dep_accessor = DependencyAccessor(dependency_manager, memory_dict)
199
+
200
+ return JarvisContext(
201
+ workflow_id=workflow_id,
202
+ step_id=step_id,
203
+ task=task,
204
+ params=params,
205
+ memory=memory_accessor,
206
+ deps=dep_accessor
207
+ )