contextagent 0.1.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 (66) hide show
  1. agentz/agent/base.py +262 -0
  2. agentz/artifacts/__init__.py +5 -0
  3. agentz/artifacts/artifact_writer.py +538 -0
  4. agentz/artifacts/reporter.py +235 -0
  5. agentz/artifacts/terminal_writer.py +100 -0
  6. agentz/context/__init__.py +6 -0
  7. agentz/context/context.py +91 -0
  8. agentz/context/conversation.py +205 -0
  9. agentz/context/data_store.py +208 -0
  10. agentz/llm/llm_setup.py +156 -0
  11. agentz/mcp/manager.py +142 -0
  12. agentz/mcp/patches.py +88 -0
  13. agentz/mcp/servers/chrome_devtools/server.py +14 -0
  14. agentz/profiles/base.py +108 -0
  15. agentz/profiles/data/data_analysis.py +38 -0
  16. agentz/profiles/data/data_loader.py +35 -0
  17. agentz/profiles/data/evaluation.py +43 -0
  18. agentz/profiles/data/model_training.py +47 -0
  19. agentz/profiles/data/preprocessing.py +47 -0
  20. agentz/profiles/data/visualization.py +47 -0
  21. agentz/profiles/manager/evaluate.py +51 -0
  22. agentz/profiles/manager/memory.py +62 -0
  23. agentz/profiles/manager/observe.py +48 -0
  24. agentz/profiles/manager/routing.py +66 -0
  25. agentz/profiles/manager/writer.py +51 -0
  26. agentz/profiles/mcp/browser.py +21 -0
  27. agentz/profiles/mcp/chrome.py +21 -0
  28. agentz/profiles/mcp/notion.py +21 -0
  29. agentz/runner/__init__.py +74 -0
  30. agentz/runner/base.py +28 -0
  31. agentz/runner/executor.py +320 -0
  32. agentz/runner/hooks.py +110 -0
  33. agentz/runner/iteration.py +142 -0
  34. agentz/runner/patterns.py +215 -0
  35. agentz/runner/tracker.py +188 -0
  36. agentz/runner/utils.py +45 -0
  37. agentz/runner/workflow.py +250 -0
  38. agentz/tools/__init__.py +20 -0
  39. agentz/tools/data_tools/__init__.py +17 -0
  40. agentz/tools/data_tools/data_analysis.py +152 -0
  41. agentz/tools/data_tools/data_loading.py +92 -0
  42. agentz/tools/data_tools/evaluation.py +175 -0
  43. agentz/tools/data_tools/helpers.py +120 -0
  44. agentz/tools/data_tools/model_training.py +192 -0
  45. agentz/tools/data_tools/preprocessing.py +229 -0
  46. agentz/tools/data_tools/visualization.py +281 -0
  47. agentz/utils/__init__.py +69 -0
  48. agentz/utils/config.py +708 -0
  49. agentz/utils/helpers.py +10 -0
  50. agentz/utils/parsers.py +142 -0
  51. agentz/utils/printer.py +539 -0
  52. contextagent-0.1.0.dist-info/METADATA +269 -0
  53. contextagent-0.1.0.dist-info/RECORD +66 -0
  54. contextagent-0.1.0.dist-info/WHEEL +5 -0
  55. contextagent-0.1.0.dist-info/licenses/LICENSE +21 -0
  56. contextagent-0.1.0.dist-info/top_level.txt +2 -0
  57. pipelines/base.py +972 -0
  58. pipelines/data_scientist.py +97 -0
  59. pipelines/data_scientist_memory.py +151 -0
  60. pipelines/experience_learner.py +0 -0
  61. pipelines/prompt_generator.py +0 -0
  62. pipelines/simple.py +78 -0
  63. pipelines/simple_browser.py +145 -0
  64. pipelines/simple_chrome.py +75 -0
  65. pipelines/simple_notion.py +103 -0
  66. pipelines/tool_builder.py +0 -0
@@ -0,0 +1,538 @@
1
+ """ArtifactWriter persists run data to markdown and HTML files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
10
+
11
+ if TYPE_CHECKING:
12
+ from agentz.artifacts.reporter import AgentStepRecord, PanelRecord
13
+
14
+
15
+ def _utc_timestamp() -> str:
16
+ """Return current UTC timestamp with second precision."""
17
+ return datetime.utcnow().replace(tzinfo=None).isoformat(timespec="seconds") + "Z"
18
+
19
+
20
+ def _json_default(obj: Any) -> Any:
21
+ """Fallback JSON serialiser for arbitrary objects."""
22
+ if hasattr(obj, "model_dump"):
23
+ return obj.model_dump()
24
+ if hasattr(obj, "__dict__"):
25
+ return obj.__dict__
26
+ return str(obj)
27
+
28
+
29
+ class ArtifactWriter:
30
+ """Collects run data and persists it as markdown and HTML artifacts."""
31
+
32
+ def __init__(
33
+ self,
34
+ *,
35
+ base_dir: Path,
36
+ pipeline_slug: str,
37
+ workflow_name: str,
38
+ experiment_id: str,
39
+ ) -> None:
40
+ self.base_dir = base_dir
41
+ self.pipeline_slug = pipeline_slug
42
+ self.workflow_name = workflow_name
43
+ self.experiment_id = experiment_id
44
+
45
+ self.run_dir = base_dir / pipeline_slug / experiment_id
46
+ self.terminal_md_path = self.run_dir / "terminal_log.md"
47
+ self.terminal_html_path = self.run_dir / "terminal_log.html"
48
+ self.final_report_md_path = self.run_dir / "final_report.md"
49
+ self.final_report_html_path = self.run_dir / "final_report.html"
50
+
51
+ self._panels: List[PanelRecord] = []
52
+ self._agent_steps: Dict[str, AgentStepRecord] = {}
53
+ self._groups: Dict[str, Dict[str, Any]] = {}
54
+ self._iterations: Dict[str, Dict[str, Any]] = {}
55
+ self._final_result: Optional[Any] = None
56
+
57
+ self._start_time: Optional[float] = None
58
+ self._started_at_iso: Optional[str] = None
59
+ self._finished_at_iso: Optional[str] = None
60
+
61
+ # ------------------------------------------------------------------ basics
62
+
63
+ def start(self, config: Any) -> None: # noqa: ARG002 - config reserved for future use
64
+ """Prepare filesystem layout and capture start metadata."""
65
+ if self._start_time is not None:
66
+ return
67
+ self.run_dir.mkdir(parents=True, exist_ok=True)
68
+ self._start_time = time.time()
69
+ self._started_at_iso = _utc_timestamp()
70
+
71
+ def set_final_result(self, result: Any) -> None:
72
+ """Store pipeline result for later persistence."""
73
+ self._final_result = result
74
+
75
+ # ----------------------------------------------------------------- logging
76
+
77
+ def record_status_update(
78
+ self,
79
+ *,
80
+ item_id: str,
81
+ content: str,
82
+ is_done: bool,
83
+ title: Optional[str],
84
+ border_style: Optional[str],
85
+ group_id: Optional[str],
86
+ ) -> None: # noqa: D401 - keeps signature compatibility
87
+ """Currently unused; maintained for interface compatibility."""
88
+ # Intentionally no-op for the simplified reporter.
89
+ return None
90
+
91
+ def record_group_start(
92
+ self,
93
+ *,
94
+ group_id: str,
95
+ title: Optional[str],
96
+ border_style: Optional[str],
97
+ iteration: Optional[int] = None,
98
+ ) -> None:
99
+ """Record the start of an iteration/group."""
100
+ timestamp = _utc_timestamp()
101
+ payload = {
102
+ "group_id": group_id,
103
+ "title": title,
104
+ "border_style": border_style,
105
+ "iteration": iteration,
106
+ "started_at": timestamp,
107
+ }
108
+ self._groups[group_id] = payload
109
+ if iteration is not None:
110
+ iter_key = f"iter-{iteration}"
111
+ self._iterations.setdefault(
112
+ iter_key,
113
+ {
114
+ "iteration": iteration,
115
+ "title": title or f"Iteration {iteration}",
116
+ "started_at": timestamp,
117
+ "finished_at": None,
118
+ "panels": [],
119
+ "agent_steps": [],
120
+ },
121
+ )
122
+
123
+ def record_group_end(
124
+ self,
125
+ *,
126
+ group_id: str,
127
+ is_done: bool = True,
128
+ title: Optional[str] = None,
129
+ ) -> None:
130
+ """Record the end of an iteration/group."""
131
+ timestamp = _utc_timestamp()
132
+ group_meta = self._groups.get(group_id)
133
+ if not group_meta:
134
+ return
135
+ group_meta.update(
136
+ {
137
+ "title": title or group_meta.get("title"),
138
+ "is_done": is_done,
139
+ "finished_at": timestamp,
140
+ }
141
+ )
142
+ iteration = group_meta.get("iteration")
143
+ if iteration is not None:
144
+ iter_key = f"iter-{iteration}"
145
+ iteration_meta = self._iterations.setdefault(
146
+ iter_key,
147
+ {
148
+ "iteration": iteration,
149
+ "title": title or f"Iteration {iteration}",
150
+ "panels": [],
151
+ "agent_steps": [],
152
+ },
153
+ )
154
+ iteration_meta["finished_at"] = timestamp
155
+
156
+ def record_agent_step_start(
157
+ self,
158
+ *,
159
+ step_id: str,
160
+ agent_name: str,
161
+ span_name: str,
162
+ iteration: Optional[int],
163
+ group_id: Optional[str],
164
+ printer_title: Optional[str],
165
+ ) -> None:
166
+ """Capture metadata when an agent step begins."""
167
+ from agentz.artifacts.reporter import AgentStepRecord
168
+
169
+ record = AgentStepRecord(
170
+ agent_name=agent_name,
171
+ span_name=span_name,
172
+ iteration=iteration,
173
+ group_id=group_id,
174
+ started_at=_utc_timestamp(),
175
+ )
176
+ self._agent_steps[step_id] = record
177
+ if iteration is not None:
178
+ iter_key = f"iter-{iteration}"
179
+ iteration_meta = self._iterations.setdefault(
180
+ iter_key,
181
+ {
182
+ "iteration": iteration,
183
+ "title": printer_title or f"Iteration {iteration}",
184
+ "panels": [],
185
+ "agent_steps": [],
186
+ },
187
+ )
188
+ iteration_meta["agent_steps"].append(record)
189
+
190
+ def record_agent_step_end(
191
+ self,
192
+ *,
193
+ step_id: str,
194
+ status: str,
195
+ duration_seconds: float,
196
+ error: Optional[str] = None,
197
+ ) -> None:
198
+ """Update agent step telemetry on completion."""
199
+ timestamp = _utc_timestamp()
200
+ record = self._agent_steps.get(step_id)
201
+ if record:
202
+ record.finished_at = timestamp
203
+ record.duration_seconds = round(duration_seconds, 3)
204
+ record.status = status
205
+ record.error = error
206
+
207
+ def record_panel(
208
+ self,
209
+ *,
210
+ title: str,
211
+ content: str,
212
+ border_style: Optional[str],
213
+ iteration: Optional[int],
214
+ group_id: Optional[str],
215
+ ) -> None:
216
+ """Persist panel meta for terminal & HTML artefacts."""
217
+ from agentz.artifacts.reporter import PanelRecord
218
+
219
+ record = PanelRecord(
220
+ title=title,
221
+ content=content,
222
+ border_style=border_style,
223
+ iteration=iteration,
224
+ group_id=group_id,
225
+ recorded_at=_utc_timestamp(),
226
+ )
227
+ self._panels.append(record)
228
+ if iteration is not None:
229
+ iter_key = f"iter-{iteration}"
230
+ iteration_meta = self._iterations.setdefault(
231
+ iter_key,
232
+ {
233
+ "iteration": iteration,
234
+ "title": f"Iteration {iteration}",
235
+ "panels": [],
236
+ "agent_steps": [],
237
+ },
238
+ )
239
+ iteration_meta["panels"].append(record)
240
+
241
+ # ------------------------------------------------------------- finalisation
242
+
243
+ def finalize(self) -> None:
244
+ """Persist markdown + HTML artefacts."""
245
+ if self._start_time is None or self._finished_at_iso is not None:
246
+ return
247
+ self._finished_at_iso = _utc_timestamp()
248
+ duration = round(time.time() - self._start_time, 3)
249
+
250
+ terminal_sections = self._build_terminal_sections()
251
+ terminal_md = self._render_terminal_markdown(duration, terminal_sections)
252
+ terminal_html = self._render_terminal_html(duration, terminal_sections)
253
+
254
+ self.terminal_md_path.write_text(terminal_md, encoding="utf-8")
255
+ self.terminal_html_path.write_text(terminal_html, encoding="utf-8")
256
+
257
+ final_md, final_html = self._render_final_report()
258
+ self.final_report_md_path.write_text(final_md, encoding="utf-8")
259
+ self.final_report_html_path.write_text(final_html, encoding="utf-8")
260
+
261
+ def _build_terminal_sections(self) -> List[Dict[str, Any]]:
262
+ """Collect ordered sections for terminal artefacts."""
263
+ sections: List[Dict[str, Any]] = []
264
+
265
+ # Iteration/scoped panels
266
+ for iter_key, meta in sorted(
267
+ self._iterations.items(),
268
+ key=lambda item: item[1].get("iteration", 0),
269
+ ):
270
+ sections.append(
271
+ {
272
+ "title": meta.get("title") or iter_key,
273
+ "started_at": meta.get("started_at"),
274
+ "finished_at": meta.get("finished_at"),
275
+ "panels": meta.get("panels", []),
276
+ "agent_steps": meta.get("agent_steps", []),
277
+ }
278
+ )
279
+
280
+ # Global panels (no iteration)
281
+ global_panels = [
282
+ record
283
+ for record in self._panels
284
+ if record.iteration is None
285
+ ]
286
+ if global_panels:
287
+ sections.append(
288
+ {
289
+ "title": "General",
290
+ "started_at": None,
291
+ "finished_at": None,
292
+ "panels": global_panels,
293
+ "agent_steps": [],
294
+ }
295
+ )
296
+
297
+ return sections
298
+
299
+ def _render_terminal_markdown(
300
+ self,
301
+ duration: float,
302
+ sections: List[Dict[str, Any]],
303
+ ) -> str:
304
+ """Render the terminal log as Markdown."""
305
+ lines: List[str] = []
306
+ lines.append(f"# Terminal Log · {self.workflow_name}")
307
+ lines.append("")
308
+ lines.append(f"- **Experiment ID:** `{self.experiment_id}`")
309
+ lines.append(f"- **Started:** {self._started_at_iso or '–'}")
310
+ lines.append(f"- **Finished:** {self._finished_at_iso or '–'}")
311
+ lines.append(f"- **Duration:** {duration} seconds")
312
+ lines.append("")
313
+
314
+ if not sections:
315
+ lines.append("_No panels recorded during this run._")
316
+ lines.append("")
317
+ return "\n".join(lines)
318
+
319
+ for section in sections:
320
+ lines.append(f"## {section['title']}")
321
+ span = ""
322
+ if section.get("started_at") or section.get("finished_at"):
323
+ span = f"{section.get('started_at', '–')} → {section.get('finished_at', '–')}"
324
+ if span:
325
+ lines.append(f"*Time:* {span}")
326
+ lines.append("")
327
+
328
+ agent_steps: List[AgentStepRecord] = section.get("agent_steps", [])
329
+ if agent_steps:
330
+ lines.append("### Agent Steps")
331
+ for step in agent_steps:
332
+ duration_txt = (
333
+ f"{step.duration_seconds}s"
334
+ if step.duration_seconds is not None
335
+ else "pending"
336
+ )
337
+ status = step.status
338
+ error = f" · Error: {step.error}" if step.error else ""
339
+ lines.append(
340
+ f"- **{step.agent_name}** · {step.span_name} "
341
+ f"({duration_txt}) · {status}{error}"
342
+ )
343
+ lines.append("")
344
+
345
+ panels: List[PanelRecord] = section.get("panels", [])
346
+ for panel in panels:
347
+ panel_title = panel.title or "Panel"
348
+ lines.append(f"### {panel_title}")
349
+ lines.append("")
350
+ lines.append("```")
351
+ lines.append(panel.content.rstrip())
352
+ lines.append("```")
353
+ lines.append("")
354
+
355
+ return "\n".join(lines).rstrip() + "\n"
356
+
357
+ def _render_terminal_html(
358
+ self,
359
+ duration: float,
360
+ sections: List[Dict[str, Any]],
361
+ ) -> str:
362
+ """Render the terminal log as standalone HTML."""
363
+ body_sections: List[str] = []
364
+
365
+ for section in sections:
366
+ panels_html: List[str] = []
367
+ for panel in section.get("panels", []):
368
+ panel_html = f"""
369
+ <article class="panel">
370
+ <h3>{panel.title or "Panel"}</h3>
371
+ <pre>{panel.content}</pre>
372
+ </article>
373
+ """.strip()
374
+ panels_html.append(panel_html)
375
+
376
+ agent_html: List[str] = []
377
+ for step in section.get("agent_steps", []):
378
+ info = json.dumps(
379
+ {
380
+ "agent": step.agent_name,
381
+ "span": step.span_name,
382
+ "status": step.status,
383
+ "duration_seconds": step.duration_seconds,
384
+ "error": step.error,
385
+ },
386
+ default=_json_default,
387
+ )
388
+ agent_html.append(f'<li><code>{info}</code></li>')
389
+
390
+ timeframe = ""
391
+ if section.get("started_at") or section.get("finished_at"):
392
+ timeframe = (
393
+ f"<p class=\"time\">{section.get('started_at', '–')} → "
394
+ f"{section.get('finished_at', '–')}</p>"
395
+ )
396
+
397
+ agents_block = ""
398
+ if agent_html:
399
+ agents_block = '<ul class="agents">' + "".join(agent_html) + "</ul>"
400
+
401
+ panels_block = "".join(panels_html)
402
+
403
+ block = (
404
+ f"\n <section class=\"section\">\n"
405
+ f" <h2>{section['title']}</h2>\n"
406
+ f" {timeframe}\n"
407
+ f" {agents_block}\n"
408
+ f" {panels_block}\n"
409
+ " </section>\n "
410
+ ).strip()
411
+ body_sections.append(block)
412
+
413
+ sections_html = "\n".join(body_sections) if body_sections else "<p>No panels recorded.</p>"
414
+
415
+ return f"""<!DOCTYPE html>
416
+ <html lang="en">
417
+ <head>
418
+ <meta charset="utf-8" />
419
+ <title>Terminal Log · {self.workflow_name}</title>
420
+ <style>
421
+ body {{
422
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
423
+ margin: 0;
424
+ padding: 24px;
425
+ background: #0f172a;
426
+ color: #e2e8f0;
427
+ }}
428
+ h1 {{
429
+ margin-top: 0;
430
+ }}
431
+ .meta {{
432
+ margin-bottom: 24px;
433
+ line-height: 1.6;
434
+ }}
435
+ .section {{
436
+ border: 1px solid rgba(148, 163, 184, 0.3);
437
+ border-radius: 12px;
438
+ padding: 16px 20px;
439
+ margin-bottom: 18px;
440
+ background: rgba(15, 23, 42, 0.6);
441
+ }}
442
+ .section h2 {{
443
+ margin-top: 0;
444
+ }}
445
+ .section .time {{
446
+ color: #60a5fa;
447
+ font-size: 0.9rem;
448
+ margin-top: -8px;
449
+ }}
450
+ pre {{
451
+ background: rgba(15, 23, 42, 0.85);
452
+ border-radius: 10px;
453
+ padding: 12px;
454
+ overflow-x: auto;
455
+ border: 1px solid rgba(148, 163, 184, 0.2);
456
+ white-space: pre-wrap;
457
+ word-wrap: break-word;
458
+ }}
459
+ ul.agents {{
460
+ list-style: none;
461
+ padding-left: 0;
462
+ margin: 0 0 16px 0;
463
+ }}
464
+ ul.agents li {{
465
+ margin-bottom: 6px;
466
+ }}
467
+ </style>
468
+ </head>
469
+ <body>
470
+ <header>
471
+ <h1>Terminal Log · {self.workflow_name}</h1>
472
+ <div class="meta">
473
+ <div><strong>Experiment ID:</strong> {self.experiment_id}</div>
474
+ <div><strong>Started:</strong> {self._started_at_iso or "–"}</div>
475
+ <div><strong>Finished:</strong> {self._finished_at_iso or "–"}</div>
476
+ <div><strong>Duration:</strong> {duration} seconds</div>
477
+ </div>
478
+ </header>
479
+ <main>
480
+ {sections_html}
481
+ </main>
482
+ </body>
483
+ </html>
484
+ """
485
+
486
+ def _render_final_report(self) -> tuple[str, str]:
487
+ """Render final report markdown + HTML."""
488
+ if isinstance(self._final_result, str):
489
+ body_md = self._final_result.rstrip()
490
+ elif self._final_result is not None:
491
+ body_md = json.dumps(self._final_result, indent=2, default=_json_default)
492
+ else:
493
+ body_md = "No final report generated."
494
+
495
+ markdown_content = f"# Final Report · {self.workflow_name}\n\n{body_md}\n"
496
+
497
+ body_pre = body_md.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
498
+ html_content = f"""<!DOCTYPE html>
499
+ <html lang="en">
500
+ <head>
501
+ <meta charset="utf-8" />
502
+ <title>Final Report · {self.workflow_name}</title>
503
+ <style>
504
+ body {{
505
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
506
+ margin: 0;
507
+ padding: 24px;
508
+ background: #111827;
509
+ color: #f9fafb;
510
+ }}
511
+ h1 {{
512
+ margin-top: 0;
513
+ }}
514
+ pre {{
515
+ background: #1f2937;
516
+ border-radius: 10px;
517
+ padding: 16px;
518
+ overflow-x: auto;
519
+ white-space: pre-wrap;
520
+ word-wrap: break-word;
521
+ border: 1px solid rgba(148, 163, 184, 0.3);
522
+ }}
523
+ </style>
524
+ </head>
525
+ <body>
526
+ <h1>Final Report · {self.workflow_name}</h1>
527
+ <pre>{body_pre}</pre>
528
+ </body>
529
+ </html>
530
+ """
531
+ return markdown_content, html_content
532
+
533
+ # ------------------------------------------------------------------ helpers
534
+
535
+ def ensure_started(self) -> None:
536
+ """Raise if reporter not initialised."""
537
+ if self._start_time is None:
538
+ raise RuntimeError("ArtifactWriter.start must be called before logging events.")