ghostlab 0.3.0__tar.gz → 0.5.0__tar.gz

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 (193) hide show
  1. ghostlab-0.5.0/CONTRIBUTING.md +57 -0
  2. ghostlab-0.5.0/PKG-INFO +148 -0
  3. ghostlab-0.5.0/README.md +112 -0
  4. ghostlab-0.5.0/docker/agent-sandbox.Dockerfile +38 -0
  5. ghostlab-0.5.0/docs/agents.md +123 -0
  6. ghostlab-0.5.0/docs/cli.md +58 -0
  7. ghostlab-0.5.0/docs/getting-started.md +62 -0
  8. ghostlab-0.5.0/docs/index.md +26 -0
  9. ghostlab-0.5.0/docs/pipelines.md +121 -0
  10. ghostlab-0.5.0/examples/README.md +20 -0
  11. ghostlab-0.5.0/examples/agents/claude.yaml +14 -0
  12. ghostlab-0.5.0/examples/agents/codex.yaml +11 -0
  13. ghostlab-0.5.0/examples/agents/copilot.yaml +11 -0
  14. ghostlab-0.5.0/examples/agents/opencode.yaml +14 -0
  15. ghostlab-0.5.0/examples/batch-label/agents/labeler.yaml +10 -0
  16. ghostlab-0.5.0/examples/batch-label/pipeline.py +37 -0
  17. ghostlab-0.5.0/examples/batch-label/tickets.jsonl +5 -0
  18. ghostlab-0.5.0/examples/mcp-eval/agents/assistant.yaml +9 -0
  19. ghostlab-0.5.0/examples/mcp-eval/agents/helper.yaml +10 -0
  20. ghostlab-0.5.0/examples/mcp-eval/pipeline.py +137 -0
  21. ghostlab-0.5.0/examples/mcp-eval/targets.jsonl +1 -0
  22. ghostlab-0.5.0/examples/release-notes/agent.yaml +16 -0
  23. ghostlab-0.5.0/ghostlab/__init__.py +27 -0
  24. {ghostlab-0.3.0/rehearsal → ghostlab-0.5.0/ghostlab}/__main__.py +0 -1
  25. ghostlab-0.5.0/ghostlab/_util.py +96 -0
  26. ghostlab-0.5.0/ghostlab/agent.py +231 -0
  27. ghostlab-0.5.0/ghostlab/cli.py +164 -0
  28. ghostlab-0.5.0/ghostlab/harnesses.py +394 -0
  29. ghostlab-0.5.0/ghostlab/parsers.py +262 -0
  30. ghostlab-0.5.0/ghostlab/pipeline.py +321 -0
  31. ghostlab-0.5.0/ghostlab/sandbox.py +446 -0
  32. ghostlab-0.5.0/ghostlab/session.py +198 -0
  33. ghostlab-0.5.0/llms.txt +33 -0
  34. {ghostlab-0.3.0 → ghostlab-0.5.0}/mkdocs.yml +3 -5
  35. {ghostlab-0.3.0 → ghostlab-0.5.0}/pyproject.toml +17 -25
  36. ghostlab-0.5.0/tests/conftest.py +62 -0
  37. ghostlab-0.5.0/tests/fakes/fake_agent.py +52 -0
  38. ghostlab-0.5.0/tests/fakes/fake_openshell.py +77 -0
  39. ghostlab-0.5.0/tests/test_agent.py +90 -0
  40. ghostlab-0.5.0/tests/test_cli.py +57 -0
  41. ghostlab-0.5.0/tests/test_harnesses.py +126 -0
  42. ghostlab-0.5.0/tests/test_parsers.py +83 -0
  43. ghostlab-0.5.0/tests/test_pipeline.py +216 -0
  44. ghostlab-0.5.0/tests/test_session.py +101 -0
  45. ghostlab-0.3.0/CONTRIBUTING.md +0 -114
  46. ghostlab-0.3.0/PKG-INFO +0 -817
  47. ghostlab-0.3.0/README.md +0 -775
  48. ghostlab-0.3.0/docs/cli.md +0 -543
  49. ghostlab-0.3.0/docs/concepts.md +0 -143
  50. ghostlab-0.3.0/docs/configured-agent-lab.md +0 -238
  51. ghostlab-0.3.0/docs/datasets-evaluation.md +0 -113
  52. ghostlab-0.3.0/docs/getting-started.md +0 -350
  53. ghostlab-0.3.0/docs/ghostlab-vision-gap-and-roadmap.md +0 -181
  54. ghostlab-0.3.0/docs/index.md +0 -36
  55. ghostlab-0.3.0/examples/README.md +0 -75
  56. ghostlab-0.3.0/examples/agent-lab/agent.json +0 -22
  57. ghostlab-0.3.0/examples/agent-lab/mcp.json +0 -5
  58. ghostlab-0.3.0/examples/agent.json +0 -50
  59. ghostlab-0.3.0/examples/demo-skill/SKILL.md +0 -9
  60. ghostlab-0.3.0/examples/mcp-config.json +0 -7
  61. ghostlab-0.3.0/examples/scenario.json +0 -18
  62. ghostlab-0.3.0/examples/target.json +0 -14
  63. ghostlab-0.3.0/ghostlab/__init__.py +0 -5
  64. ghostlab-0.3.0/ghostlab/__main__.py +0 -4
  65. ghostlab-0.3.0/ghostlab/cli.py +0 -5
  66. ghostlab-0.3.0/llms.txt +0 -71
  67. ghostlab-0.3.0/rehearsal/__init__.py +0 -3
  68. ghostlab-0.3.0/rehearsal/agent_profile.py +0 -290
  69. ghostlab-0.3.0/rehearsal/agent_sandbox.py +0 -324
  70. ghostlab-0.3.0/rehearsal/agents.py +0 -251
  71. ghostlab-0.3.0/rehearsal/apps_host/__init__.py +0 -30
  72. ghostlab-0.3.0/rehearsal/apps_host/assertions.py +0 -102
  73. ghostlab-0.3.0/rehearsal/apps_host/executor.py +0 -125
  74. ghostlab-0.3.0/rehearsal/apps_host/live.py +0 -356
  75. ghostlab-0.3.0/rehearsal/apps_host/protocol.py +0 -246
  76. ghostlab-0.3.0/rehearsal/apps_host/renderer.py +0 -270
  77. ghostlab-0.3.0/rehearsal/apps_host/report.py +0 -123
  78. ghostlab-0.3.0/rehearsal/cli.py +0 -3438
  79. ghostlab-0.3.0/rehearsal/cli_ui.py +0 -118
  80. ghostlab-0.3.0/rehearsal/codex_backend.py +0 -153
  81. ghostlab-0.3.0/rehearsal/compare.py +0 -117
  82. ghostlab-0.3.0/rehearsal/config.py +0 -221
  83. ghostlab-0.3.0/rehearsal/contract.py +0 -501
  84. ghostlab-0.3.0/rehearsal/critique.py +0 -243
  85. ghostlab-0.3.0/rehearsal/dashboard.py +0 -520
  86. ghostlab-0.3.0/rehearsal/dataset.py +0 -302
  87. ghostlab-0.3.0/rehearsal/evaluate.py +0 -513
  88. ghostlab-0.3.0/rehearsal/generate.py +0 -230
  89. ghostlab-0.3.0/rehearsal/hosts/__init__.py +0 -84
  90. ghostlab-0.3.0/rehearsal/hosts/base.py +0 -80
  91. ghostlab-0.3.0/rehearsal/hosts/direct.py +0 -251
  92. ghostlab-0.3.0/rehearsal/hosts/runner.py +0 -320
  93. ghostlab-0.3.0/rehearsal/inspect.py +0 -243
  94. ghostlab-0.3.0/rehearsal/jobs.py +0 -530
  95. ghostlab-0.3.0/rehearsal/lab.py +0 -296
  96. ghostlab-0.3.0/rehearsal/llm_backend.py +0 -90
  97. ghostlab-0.3.0/rehearsal/logging.py +0 -17
  98. ghostlab-0.3.0/rehearsal/mcp_apps.py +0 -518
  99. ghostlab-0.3.0/rehearsal/mcp_client.py +0 -361
  100. ghostlab-0.3.0/rehearsal/mcp_config.py +0 -36
  101. ghostlab-0.3.0/rehearsal/mcp_targets.py +0 -128
  102. ghostlab-0.3.0/rehearsal/opencode_backend.py +0 -201
  103. ghostlab-0.3.0/rehearsal/opencode_config.py +0 -223
  104. ghostlab-0.3.0/rehearsal/orchestrator.py +0 -389
  105. ghostlab-0.3.0/rehearsal/personas.py +0 -147
  106. ghostlab-0.3.0/rehearsal/plan.py +0 -619
  107. ghostlab-0.3.0/rehearsal/plan_generate.py +0 -151
  108. ghostlab-0.3.0/rehearsal/profile.py +0 -240
  109. ghostlab-0.3.0/rehearsal/prompts.py +0 -326
  110. ghostlab-0.3.0/rehearsal/readiness.py +0 -386
  111. ghostlab-0.3.0/rehearsal/report.py +0 -68
  112. ghostlab-0.3.0/rehearsal/resolved_config.py +0 -131
  113. ghostlab-0.3.0/rehearsal/review.py +0 -248
  114. ghostlab-0.3.0/rehearsal/rollout_report.py +0 -327
  115. ghostlab-0.3.0/rehearsal/runner_presets.py +0 -246
  116. ghostlab-0.3.0/rehearsal/runners.py +0 -293
  117. ghostlab-0.3.0/rehearsal/sampling.py +0 -300
  118. ghostlab-0.3.0/rehearsal/sandbox.py +0 -527
  119. ghostlab-0.3.0/rehearsal/scorecard.py +0 -266
  120. ghostlab-0.3.0/rehearsal/setup_runtime.py +0 -331
  121. ghostlab-0.3.0/rehearsal/skills.py +0 -47
  122. ghostlab-0.3.0/rehearsal/spec.py +0 -664
  123. ghostlab-0.3.0/rehearsal/storage/__init__.py +0 -19
  124. ghostlab-0.3.0/rehearsal/storage/db.py +0 -136
  125. ghostlab-0.3.0/rehearsal/storage/hashing.py +0 -19
  126. ghostlab-0.3.0/rehearsal/storage/ids.py +0 -44
  127. ghostlab-0.3.0/rehearsal/storage/migrations/0001_initial.sql +0 -349
  128. ghostlab-0.3.0/rehearsal/storage/redact.py +0 -46
  129. ghostlab-0.3.0/rehearsal/storage/repository.py +0 -1036
  130. ghostlab-0.3.0/rehearsal/termcolor.py +0 -96
  131. ghostlab-0.3.0/rehearsal/testrun.py +0 -298
  132. ghostlab-0.3.0/rehearsal/tool_capture.py +0 -326
  133. ghostlab-0.3.0/rehearsal/types.py +0 -30
  134. ghostlab-0.3.0/rehearsal/ui/__init__.py +0 -1
  135. ghostlab-0.3.0/rehearsal/ui/app.py +0 -733
  136. ghostlab-0.3.0/runners/claude-process.example.json +0 -7
  137. ghostlab-0.3.0/runners/codex-cortex-aut.json +0 -20
  138. ghostlab-0.3.0/runners/codex-cortex-local-aut.json +0 -20
  139. ghostlab-0.3.0/runners/codex-cortex-local-session.json +0 -20
  140. ghostlab-0.3.0/runners/codex-process.example.json +0 -7
  141. ghostlab-0.3.0/runners/codex-user-emulator.json +0 -16
  142. ghostlab-0.3.0/runners/mock-aut.json +0 -4
  143. ghostlab-0.3.0/runners/mock-user.json +0 -4
  144. ghostlab-0.3.0/tests/test_agents.py +0 -83
  145. ghostlab-0.3.0/tests/test_apps_host.py +0 -203
  146. ghostlab-0.3.0/tests/test_apps_live.py +0 -126
  147. ghostlab-0.3.0/tests/test_cli_spec_flow.py +0 -608
  148. ghostlab-0.3.0/tests/test_codex_backend_sandbox.py +0 -47
  149. ghostlab-0.3.0/tests/test_compare.py +0 -53
  150. ghostlab-0.3.0/tests/test_config.py +0 -67
  151. ghostlab-0.3.0/tests/test_configured_agent.py +0 -383
  152. ghostlab-0.3.0/tests/test_contract.py +0 -216
  153. ghostlab-0.3.0/tests/test_critique.py +0 -107
  154. ghostlab-0.3.0/tests/test_dataset.py +0 -72
  155. ghostlab-0.3.0/tests/test_evaluate.py +0 -211
  156. ghostlab-0.3.0/tests/test_generate.py +0 -50
  157. ghostlab-0.3.0/tests/test_hosts_runner.py +0 -304
  158. ghostlab-0.3.0/tests/test_inspect_lint.py +0 -80
  159. ghostlab-0.3.0/tests/test_jobs.py +0 -241
  160. ghostlab-0.3.0/tests/test_mcp_apps.py +0 -231
  161. ghostlab-0.3.0/tests/test_mcp_client.py +0 -72
  162. ghostlab-0.3.0/tests/test_mcp_targets.py +0 -110
  163. ghostlab-0.3.0/tests/test_opencode_backend.py +0 -286
  164. ghostlab-0.3.0/tests/test_orchestrator.py +0 -53
  165. ghostlab-0.3.0/tests/test_personas.py +0 -72
  166. ghostlab-0.3.0/tests/test_plan.py +0 -201
  167. ghostlab-0.3.0/tests/test_plan_generate.py +0 -105
  168. ghostlab-0.3.0/tests/test_profile.py +0 -39
  169. ghostlab-0.3.0/tests/test_prompts.py +0 -32
  170. ghostlab-0.3.0/tests/test_readiness.py +0 -169
  171. ghostlab-0.3.0/tests/test_review.py +0 -92
  172. ghostlab-0.3.0/tests/test_runner_presets.py +0 -60
  173. ghostlab-0.3.0/tests/test_sampling.py +0 -187
  174. ghostlab-0.3.0/tests/test_sandbox.py +0 -132
  175. ghostlab-0.3.0/tests/test_sandbox_stdio.py +0 -213
  176. ghostlab-0.3.0/tests/test_scorecard.py +0 -112
  177. ghostlab-0.3.0/tests/test_session_runner.py +0 -46
  178. ghostlab-0.3.0/tests/test_setup_runtime.py +0 -160
  179. ghostlab-0.3.0/tests/test_skills.py +0 -39
  180. ghostlab-0.3.0/tests/test_spec.py +0 -160
  181. ghostlab-0.3.0/tests/test_storage.py +0 -322
  182. ghostlab-0.3.0/tests/test_testrun.py +0 -276
  183. ghostlab-0.3.0/tests/test_tool_capture.py +0 -186
  184. ghostlab-0.3.0/tests/test_widgets_and_dashboard.py +0 -143
  185. {ghostlab-0.3.0 → ghostlab-0.5.0}/.gitignore +0 -0
  186. {ghostlab-0.3.0 → ghostlab-0.5.0}/LICENSE +0 -0
  187. {ghostlab-0.3.0 → ghostlab-0.5.0}/docs/publishing.md +0 -0
  188. {ghostlab-0.3.0/examples/agent-lab → ghostlab-0.5.0/examples/mcp-eval}/tiny-mcp.js +0 -0
  189. {ghostlab-0.3.0/examples/agent-lab → ghostlab-0.5.0/examples/release-notes}/AGENTS.md +0 -0
  190. {ghostlab-0.3.0/examples/agent-lab → ghostlab-0.5.0/examples/release-notes}/repo/CHANGELOG.md +0 -0
  191. {ghostlab-0.3.0/examples/agent-lab → ghostlab-0.5.0/examples/release-notes}/repo/README.md +0 -0
  192. {ghostlab-0.3.0/examples/agent-lab → ghostlab-0.5.0/examples/release-notes}/repo/src/app.py +0 -0
  193. {ghostlab-0.3.0/examples/agent-lab → ghostlab-0.5.0/examples/release-notes}/skills/release-notes/SKILL.md +0 -0
@@ -0,0 +1,57 @@
1
+ # Contributing to Ghostlab
2
+
3
+ Thanks for helping improve Ghostlab! This guide is written for both humans and
4
+ coding agents. For a machine-readable map of the project, see
5
+ [`llms.txt`](llms.txt).
6
+
7
+ ## Project shape
8
+
9
+ - Package and CLI: `ghostlab`. Its only runtime dependency is PyYAML.
10
+ - Layers, bottom up: `sandbox` (local / OpenShell boxes) → `harnesses` +
11
+ `parsers` (one class per agent CLI) → `session` (a run, single or
12
+ multi-turn) → `pipeline` (stages, checkpoints, resume) → `cli`.
13
+ - Ghostlab is infrastructure. Job-specific logic belongs in pipelines under
14
+ `examples/`, not in the package.
15
+
16
+ ## Setup
17
+
18
+ ```bash
19
+ python3 -m venv .venv # Python 3.10+
20
+ .venv/bin/pip install -e '.[dev]'
21
+ .venv/bin/pytest
22
+ ```
23
+
24
+ Sandboxed runs need [NVIDIA OpenShell](https://docs.nvidia.com/openshell/latest/)
25
+ (`openshell status`). The test suite does not: `tests/fakes/` provides a fake
26
+ agent CLI and a fake `openshell` CLI.
27
+
28
+ ## Making a change
29
+
30
+ 1. Branch off `main` (`feat/…`, `fix/…`, `docs/…`).
31
+ 2. Make the change with tests.
32
+ 3. Open a PR against `main` that says what changed and why.
33
+
34
+ To add a harness, subclass `Harness` in `ghostlab/harnesses.py`, add its parser
35
+ to `ghostlab/parsers.py`, register both, and document its options in
36
+ `docs/agents.md`.
37
+
38
+ When you add or change a CLI command, update `ghostlab/cli.py`, `tests/`,
39
+ `README.md`, and `docs/cli.md`.
40
+
41
+ ## Pull request checklist
42
+
43
+ ```bash
44
+ .venv/bin/pytest # always
45
+ .venv/bin/mkdocs build --strict # if you touched docs/ or mkdocs.yml
46
+ .venv/bin/python -m build # if you touched packaging
47
+ .venv/bin/twine check dist/* # "
48
+ ```
49
+
50
+ ## Releases
51
+
52
+ Publishing to PyPI is automated by `.github/workflows/publish.yml` via PyPI
53
+ Trusted Publishing. It runs when a GitHub Release is published.
54
+
55
+ 1. Bump `__version__` in `ghostlab/__init__.py`.
56
+ 2. Validate: `.venv/bin/pytest && .venv/bin/python -m build && .venv/bin/twine check dist/*`.
57
+ 3. `gh release create v<x.y.z> --generate-notes`.
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.5
2
+ Name: ghostlab
3
+ Version: 0.5.0
4
+ Summary: A headless runner for coding agents (Claude Code, Codex, Copilot, OpenCode), sandboxed in NVIDIA OpenShell, with a Python SDK for checkpointed, resumable agent pipelines: evals, labeling, and batch jobs.
5
+ Project-URL: Documentation, https://sajjadgg.github.io/Ghostlab/
6
+ Project-URL: Homepage, https://github.com/sajjadGG/Ghostlab
7
+ Project-URL: Repository, https://github.com/sajjadGG/Ghostlab
8
+ Project-URL: Issues, https://github.com/sajjadGG/Ghostlab/issues
9
+ Project-URL: Changelog, https://github.com/sajjadGG/Ghostlab/releases
10
+ Author: SajjadGG
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: agents,ai-agents,batch,claude-code,codex,evaluation,github-copilot,headless,labeling,llm-evaluation,mcp,opencode,openshell,pipeline,sandbox
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Software Development :: Testing
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: pyyaml>=6
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: mkdocs>=1.6; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: twine>=5.0; extra == 'dev'
33
+ Provides-Extra: docs
34
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
35
+ Description-Content-Type: text/markdown
36
+
37
+ <p align="center"><img src="static/ghostlablogo.png" alt="Ghostlab" width="160"></p>
38
+
39
+ # Ghostlab
40
+
41
+ **A headless runner for coding agents, and a Python SDK for composing them
42
+ into checkpointed, resumable jobs.**
43
+
44
+ Define an agent once — harness, model, instructions, skills, MCP servers,
45
+ workspace — and run it in **Claude Code, Codex, GitHub Copilot CLI, or
46
+ OpenCode**, on your machine or inside an **NVIDIA OpenShell** sandbox. Then
47
+ chain agent calls into pipelines for evaluations, labeling, testing, or any
48
+ batch job: every item and every agent call is checkpointed, so an interrupted
49
+ run picks up where it stopped.
50
+
51
+ Ghostlab is infrastructure. It has no opinion about what your job does — an
52
+ MCP evaluation is just [one example pipeline](examples/mcp-eval/pipeline.py).
53
+
54
+ ```bash
55
+ pip install ghostlab
56
+ ```
57
+
58
+ ## Run an agent
59
+
60
+ ```yaml
61
+ # agent.yaml
62
+ name: reviewer
63
+ harness: claude # claude | codex | copilot | opencode | command
64
+ model: sonnet
65
+ instructions: Be concise.
66
+ skills: [skills/review]
67
+ mcps:
68
+ docs: {command: node, args: [docs-mcp.js]}
69
+ workspace: repo # the agent works on a private copy
70
+ sandbox: {backend: openshell, providers: [anthropic]}
71
+ ```
72
+
73
+ ```bash
74
+ ghostlab exec --agent agent.yaml "Review src/app.py" --collect REVIEW.md
75
+ ```
76
+
77
+ ```python
78
+ from ghostlab import Agent
79
+
80
+ agent = Agent.load("agent.yaml")
81
+ result = agent.run("Review src/app.py and reply with JSON")
82
+ result.status, result.answer, result.tool_calls, result.json()
83
+
84
+ with agent.session() as chat: # multi-turn: the harness session resumes
85
+ chat.send("Find the bug")
86
+ chat.send("Now fix it")
87
+ ```
88
+
89
+ ## Compose a pipeline
90
+
91
+ ```python
92
+ from ghostlab import Agent, Pipeline, stage
93
+
94
+ @stage # one output per item
95
+ def describe(ctx, target):
96
+ explorer = ctx.agent("assistant").with_mcp(target["id"], target["mcp"])
97
+ return {**target, "capabilities": ctx.run(explorer, "What can this MCP do? JSON.").json()}
98
+
99
+ @stage(fan_out=True) # a list: each element becomes an item
100
+ def scenarios(ctx, target):
101
+ return ctx.run(ctx.agent("planner"), f"Write test goals for {target}. JSON list.").json()
102
+
103
+ @stage(concurrency=4, retries=1) # parallel, retried on failure
104
+ def evaluate(ctx, scenario):
105
+ ...
106
+
107
+ @stage(gather=True) # every surviving item at once
108
+ def report(ctx, results):
109
+ ...
110
+
111
+ pipeline = Pipeline("mcp-eval", [describe, scenarios, evaluate, report],
112
+ params={"assistant": Agent.load("agents/assistant.yaml"),
113
+ "planner": Agent.load("agents/planner.yaml")})
114
+ ```
115
+
116
+ ```bash
117
+ ghostlab run pipeline.py --input targets.jsonl # run
118
+ ghostlab run pipeline.py # resume
119
+ ghostlab run pipeline.py --param assistant=agents/codex.yaml # swap an agent
120
+ ghostlab status runs/mcp-eval
121
+ ```
122
+
123
+ - **Checkpointed:** each item's outcome is saved as it finishes. So is each
124
+ agent call inside an item, so an item that failed halfway reuses the calls
125
+ that completed.
126
+ - **Resumable:** re-running skips done items and retries failed ones. A changed
127
+ input, parameter, or stage `version` recomputes only what it affects.
128
+ - **Classified failures:** `timed_out`, `model_unavailable`, `sandbox_error`,
129
+ `harness_error`, and `agent_error` are kept apart, so "the agent got it wrong"
130
+ never looks like "the infrastructure broke".
131
+
132
+ ## Examples
133
+
134
+ | Example | What it shows |
135
+ | --- | --- |
136
+ | [`examples/mcp-eval`](examples/mcp-eval/pipeline.py) | describe an MCP → generate scenarios → emulated-user conversations judged by an agent → report |
137
+ | [`examples/batch-label`](examples/batch-label/pipeline.py) | label a dataset with eight agents in parallel, validating every answer |
138
+ | [`examples/release-notes`](examples/release-notes/agent.yaml) | one configured agent with instructions, a skill, and a workspace |
139
+ | [`examples/agents`](examples/agents) | a starting config for each harness |
140
+
141
+ ## Documentation
142
+
143
+ <https://sajjadgg.github.io/Ghostlab/> — [getting started](docs/getting-started.md),
144
+ [agents](docs/agents.md), [pipelines](docs/pipelines.md), [CLI](docs/cli.md).
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,112 @@
1
+ <p align="center"><img src="static/ghostlablogo.png" alt="Ghostlab" width="160"></p>
2
+
3
+ # Ghostlab
4
+
5
+ **A headless runner for coding agents, and a Python SDK for composing them
6
+ into checkpointed, resumable jobs.**
7
+
8
+ Define an agent once — harness, model, instructions, skills, MCP servers,
9
+ workspace — and run it in **Claude Code, Codex, GitHub Copilot CLI, or
10
+ OpenCode**, on your machine or inside an **NVIDIA OpenShell** sandbox. Then
11
+ chain agent calls into pipelines for evaluations, labeling, testing, or any
12
+ batch job: every item and every agent call is checkpointed, so an interrupted
13
+ run picks up where it stopped.
14
+
15
+ Ghostlab is infrastructure. It has no opinion about what your job does — an
16
+ MCP evaluation is just [one example pipeline](examples/mcp-eval/pipeline.py).
17
+
18
+ ```bash
19
+ pip install ghostlab
20
+ ```
21
+
22
+ ## Run an agent
23
+
24
+ ```yaml
25
+ # agent.yaml
26
+ name: reviewer
27
+ harness: claude # claude | codex | copilot | opencode | command
28
+ model: sonnet
29
+ instructions: Be concise.
30
+ skills: [skills/review]
31
+ mcps:
32
+ docs: {command: node, args: [docs-mcp.js]}
33
+ workspace: repo # the agent works on a private copy
34
+ sandbox: {backend: openshell, providers: [anthropic]}
35
+ ```
36
+
37
+ ```bash
38
+ ghostlab exec --agent agent.yaml "Review src/app.py" --collect REVIEW.md
39
+ ```
40
+
41
+ ```python
42
+ from ghostlab import Agent
43
+
44
+ agent = Agent.load("agent.yaml")
45
+ result = agent.run("Review src/app.py and reply with JSON")
46
+ result.status, result.answer, result.tool_calls, result.json()
47
+
48
+ with agent.session() as chat: # multi-turn: the harness session resumes
49
+ chat.send("Find the bug")
50
+ chat.send("Now fix it")
51
+ ```
52
+
53
+ ## Compose a pipeline
54
+
55
+ ```python
56
+ from ghostlab import Agent, Pipeline, stage
57
+
58
+ @stage # one output per item
59
+ def describe(ctx, target):
60
+ explorer = ctx.agent("assistant").with_mcp(target["id"], target["mcp"])
61
+ return {**target, "capabilities": ctx.run(explorer, "What can this MCP do? JSON.").json()}
62
+
63
+ @stage(fan_out=True) # a list: each element becomes an item
64
+ def scenarios(ctx, target):
65
+ return ctx.run(ctx.agent("planner"), f"Write test goals for {target}. JSON list.").json()
66
+
67
+ @stage(concurrency=4, retries=1) # parallel, retried on failure
68
+ def evaluate(ctx, scenario):
69
+ ...
70
+
71
+ @stage(gather=True) # every surviving item at once
72
+ def report(ctx, results):
73
+ ...
74
+
75
+ pipeline = Pipeline("mcp-eval", [describe, scenarios, evaluate, report],
76
+ params={"assistant": Agent.load("agents/assistant.yaml"),
77
+ "planner": Agent.load("agents/planner.yaml")})
78
+ ```
79
+
80
+ ```bash
81
+ ghostlab run pipeline.py --input targets.jsonl # run
82
+ ghostlab run pipeline.py # resume
83
+ ghostlab run pipeline.py --param assistant=agents/codex.yaml # swap an agent
84
+ ghostlab status runs/mcp-eval
85
+ ```
86
+
87
+ - **Checkpointed:** each item's outcome is saved as it finishes. So is each
88
+ agent call inside an item, so an item that failed halfway reuses the calls
89
+ that completed.
90
+ - **Resumable:** re-running skips done items and retries failed ones. A changed
91
+ input, parameter, or stage `version` recomputes only what it affects.
92
+ - **Classified failures:** `timed_out`, `model_unavailable`, `sandbox_error`,
93
+ `harness_error`, and `agent_error` are kept apart, so "the agent got it wrong"
94
+ never looks like "the infrastructure broke".
95
+
96
+ ## Examples
97
+
98
+ | Example | What it shows |
99
+ | --- | --- |
100
+ | [`examples/mcp-eval`](examples/mcp-eval/pipeline.py) | describe an MCP → generate scenarios → emulated-user conversations judged by an agent → report |
101
+ | [`examples/batch-label`](examples/batch-label/pipeline.py) | label a dataset with eight agents in parallel, validating every answer |
102
+ | [`examples/release-notes`](examples/release-notes/agent.yaml) | one configured agent with instructions, a skill, and a workspace |
103
+ | [`examples/agents`](examples/agents) | a starting config for each harness |
104
+
105
+ ## Documentation
106
+
107
+ <https://sajjadgg.github.io/Ghostlab/> — [getting started](docs/getting-started.md),
108
+ [agents](docs/agents.md), [pipelines](docs/pipelines.md), [CLI](docs/cli.md).
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,38 @@
1
+ # Sandbox image for running a configured agent under test.
2
+ #
3
+ # The agent CLI must run *inside* the sandbox, not on the host: an agent
4
+ # configured with bash/edit permissions is exactly the configuration worth
5
+ # testing and the least safe to run on a developer's machine. The host's own
6
+ # Agent binaries cannot be uploaded — they are platform-specific executables —
7
+ # so the Linux builds are installed here instead.
8
+ #
9
+ # Build is handled by OpenShell:
10
+ # sandbox:
11
+ # image: docker/agent-sandbox.Dockerfile
12
+ # which resolves to `openshell sandbox create --from <this file>`.
13
+ FROM ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:aeef1c63f00e2913ea002ccb3aaf925f338b5c5d70e63576f0d95c16a138044e
14
+
15
+ USER root
16
+
17
+ # Pinned so a sandbox rebuild cannot silently change the agent runtime under an
18
+ # evaluation. Bump deliberately, and re-record the version in run artifacts.
19
+ ARG OPENCODE_VERSION=1.4.3
20
+ ARG COPILOT_VERSION=1.0.80
21
+ RUN npm install -g \
22
+ "opencode-ai@${OPENCODE_VERSION}" \
23
+ "@github/copilot@${COPILOT_VERSION}" \
24
+ && opencode --version \
25
+ && copilot --version
26
+
27
+ # OpenCode keeps credentials and session state under XDG paths. Fixing them
28
+ # outside $HOME keeps them off the uploaded workspace, so an agent with `edit`
29
+ # permission cannot read or rewrite its own auth file.
30
+ ENV XDG_DATA_HOME=/opt/agent/share \
31
+ XDG_CONFIG_HOME=/opt/agent/config \
32
+ XDG_CACHE_HOME=/opt/agent/cache
33
+ RUN mkdir -p /opt/agent/share/opencode /opt/agent/config /opt/agent/cache \
34
+ && chown -R 998:998 /opt/agent \
35
+ && chmod 700 /opt/agent/share/opencode
36
+
37
+ USER 998
38
+ WORKDIR /sandbox
@@ -0,0 +1,123 @@
1
+ # Agents
2
+
3
+ An agent is a harness plus what it is given to work with. Load one from YAML
4
+ or JSON with `Agent.load(path)`, or build it in Python with `Agent(...)`.
5
+ Relative paths in a config file resolve against that file.
6
+
7
+ ```yaml
8
+ name: reviewer # default: the file name
9
+ harness: claude # claude | codex | copilot | opencode | command
10
+ model: sonnet # passed to the harness as-is
11
+ instructions: Be concise. # free text
12
+ instruction_files: [AGENTS.md] # files whose contents are instructions
13
+ skills: [skills/review] # SKILL.md files or their folders
14
+ mcps: # the `mcpServers` shape every client uses
15
+ docs: {command: node, args: [docs-mcp.js], env: {LEVEL: "1"}}
16
+ hub: {url: "https://hub.example/mcp", headers: {Authorization: "Bearer ${HUB_TOKEN}"}}
17
+ mcp_config: .mcp.json # or load servers from an existing config file
18
+ workspace: repo # a directory the agent works on (always a copy)
19
+ env: {API_BASE: "${API_BASE}"} # extra environment; ${VAR} expands on the host
20
+ timeout: 900 # seconds per turn
21
+ options: {} # harness-specific settings, below
22
+ sandbox: {backend: openshell} # where it runs, below
23
+ ```
24
+
25
+ Agents are immutable. Derive variants with `with_` and `with_mcp`:
26
+
27
+ ```python
28
+ judge = helper.with_(model="opus", instructions="You are a strict grader.")
29
+ tester = assistant.with_mcp("tiny", {"command": "node", "args": ["tiny-mcp.js"]})
30
+ ```
31
+
32
+ ## Running
33
+
34
+ | Call | Does |
35
+ | --- | --- |
36
+ | `agent.run(prompt, run_dir=None, collect=())` | one task; returns a `Result` |
37
+ | `agent.session(run_dir=None)` | a multi-turn `Session`; `session.send(prompt)` per turn |
38
+
39
+ A session keeps one sandbox and one harness conversation alive. Later turns
40
+ resume it with `--resume` (Claude Code), `exec resume` (Codex),
41
+ `--session-id`/`--continue` (Copilot), or `--session` (OpenCode). Instructions
42
+ and skills are sent on the first turn only.
43
+
44
+ `collect` copies workspace paths into the turn directory before the sandbox is
45
+ torn down; they are listed in `result.files`.
46
+
47
+ ### Result
48
+
49
+ | Field | Meaning |
50
+ | --- | --- |
51
+ | `status` | `completed`, `agent_error`, `timed_out`, `model_unavailable`, `sandbox_error`, `harness_error` |
52
+ | `ok` | `status == "completed"` |
53
+ | `answer` | the agent's final message |
54
+ | `json()` | the JSON value in the answer (tolerates prose and code fences) |
55
+ | `check()` | returns the result, or raises `AgentError` if it did not complete |
56
+ | `tool_calls` / `builtin_calls` | MCP calls and built-in tool calls: server, tool, status, arguments, result, error |
57
+ | `session_id`, `turn`, `duration`, `exit_code`, `error`, `files`, `dir` | the rest of the trace |
58
+
59
+ Each turn writes `prompt.txt`, `stdout.txt`, `stderr.txt`, `answer.md`,
60
+ `result.json`, and `files/` under `<run_dir>/turn-N/`.
61
+
62
+ ## Harnesses
63
+
64
+ Instruction files and skills are rendered into a preamble ahead of the task
65
+ for every harness except OpenCode, which loads them natively. Every harness
66
+ accepts `options.bin` (host binary) and `options.extra_args` (appended
67
+ verbatim).
68
+
69
+ | Harness | Command | MCPs via | Options |
70
+ | --- | --- | --- | --- |
71
+ | `claude` | `claude -p --output-format stream-json` | `--mcp-config` (pre-approved) | `permission_mode`, `max_turns`, `allow_tools`, `deny_tools` |
72
+ | `codex` | `codex exec --json` | `-c mcp_servers.<name>.*` | `sandbox_mode`, `approval`, `reasoning_effort` |
73
+ | `copilot` | `copilot --output-format json --prompt` | `--additional-mcp-config` | `agent`, `reasoning_effort`, `mode`, `allow_all`, `allow_all_tools`, `allow_tools`, `deny_tools`, `add_dirs`, `allow_urls`, `deny_urls`, `plugin_dirs`, ... |
74
+ | `opencode` | `opencode run --format json` | its own config (`OPENCODE_CONFIG_CONTENT`) | `default_agent`, `config` (merged into `opencode.json`) |
75
+ | `command` | `options.command` | — | `command` (argv), `prompt` (`stdin` or `arg`), `output` (`text`, or a harness name to parse its stream) |
76
+
77
+ Codex reads `Bearer ${VAR}` and `${VAR}` headers from the environment itself,
78
+ so those secrets never appear in its argv. OpenCode denies `bash`, `edit`, and
79
+ `webfetch` unless `options.config.permission` allows them.
80
+
81
+ Coding-agent sessions are tagged as synthetic: Codex gets
82
+ `originator: ghostlab`, and Copilot session ids start with Ghostlab's reserved
83
+ UUID prefix, so rollout collectors can exclude them.
84
+
85
+ ## Sandbox
86
+
87
+ ```yaml
88
+ sandbox:
89
+ backend: openshell # or local
90
+ image: base # an OpenShell image or Dockerfile
91
+ providers: [anthropic] # OpenShell providers holding credentials
92
+ policy: egress.yaml # an OpenShell policy file
93
+ env_allowlist: [GH_TOKEN] # host variables forwarded into the sandbox
94
+ uploads: [{source: data, target: /sandbox/data}]
95
+ files: {/opt/agent/share/opencode/auth.json: ~/.local/share/opencode/auth.json}
96
+ cpu: "2"
97
+ memory: 4Gi
98
+ keep: false # leave the sandbox running for debugging
99
+ ```
100
+
101
+ `sandbox: local` is shorthand for `{backend: local}`.
102
+
103
+ **OpenShell** (the default) creates one sandbox per session. It uploads the
104
+ workspace to `/sandbox/<name>`, every stdio MCP's program to `/sandbox/mcp`,
105
+ and any files the harness reads by path to `/sandbox/agent`. Paths in the
106
+ command are rewritten to those locations. `files` are copied in after creation
107
+ with mode 600, so credentials can live outside the workspace. With no
108
+ providers the sandbox is created with `--no-auto-providers`. For OpenCode,
109
+ when no `policy` is given, Ghostlab generates a least-privilege egress policy
110
+ for the model's provider. The sandbox log is saved as `openshell.log`, and the
111
+ sandbox is deleted unless `keep` is set. OpenShell failures are reported as
112
+ `sandbox_error` and never fall back to local execution.
113
+
114
+ **Local** runs the harness on the host, in a private copy of the workspace,
115
+ with the full host environment. The copy sits in a neutral temporary directory
116
+ while the agent runs — harnesses tell the model their working directory, and a
117
+ run directory's path names the job and item — and is moved to
118
+ `<run_dir>/workspace` afterwards. `uploads`, `files`,
119
+ and the other OpenShell settings do not apply. Use it only for agents and MCPs
120
+ you trust.
121
+
122
+ The image in [`docker/agent-sandbox.Dockerfile`](https://github.com/sajjadGG/Ghostlab/blob/main/docker/agent-sandbox.Dockerfile)
123
+ pins OpenCode and Copilot CLI builds and keeps their state outside the workspace.
@@ -0,0 +1,58 @@
1
+ # CLI
2
+
3
+ ```text
4
+ ghostlab exec run one agent on one task
5
+ ghostlab run run or resume a pipeline
6
+ ghostlab status summarize a pipeline run
7
+ ```
8
+
9
+ Exit codes: `0` success, `1` the task or some items failed, `2` invalid
10
+ configuration.
11
+
12
+ ## `ghostlab exec`
13
+
14
+ ```bash
15
+ ghostlab exec --agent agent.yaml "Draft release notes into RELEASE.md" --collect RELEASE.md
16
+ echo "Summarize this" | ghostlab exec --agent agent.yaml -
17
+ ```
18
+
19
+ | Flag | Meaning |
20
+ | --- | --- |
21
+ | `--agent PATH` | agent config (required) |
22
+ | `TASK` / `-` / `--prompt-file PATH` | the task, inline, from stdin, or from a file |
23
+ | `--harness`, `--model`, `--timeout`, `--workspace` | override the config |
24
+ | `--sandbox openshell\|local` | override the execution boundary |
25
+ | `--provider NAME` | attach an OpenShell provider (repeatable) |
26
+ | `--collect PATH` | copy a workspace path into the run dir (repeatable) |
27
+ | `--run-dir DIR` | where to keep the trace (default `runs/<timestamp>-<agent>`) |
28
+ | `--json` | print the full result instead of the answer |
29
+
30
+ The answer goes to stdout, and the status line and run directory go to stderr.
31
+
32
+ ## `ghostlab run`
33
+
34
+ ```bash
35
+ ghostlab run examples/mcp-eval/pipeline.py --input examples/mcp-eval/targets.jsonl
36
+ ghostlab run examples/mcp-eval/pipeline.py # resume
37
+ ghostlab run pipeline.py:nightly --params params.yaml --param max_turns=6
38
+ ```
39
+
40
+ | Flag | Meaning |
41
+ | --- | --- |
42
+ | `FILE.py[:NAME]` | the module holding the `Pipeline` (`NAME` when it defines several) |
43
+ | `--input PATH` | items as `.jsonl`, JSON, or YAML (repeatable); omit to reuse the recorded inputs |
44
+ | `--param KEY=VALUE` | a parameter; `VALUE` is parsed as JSON when it can be, otherwise kept as a string |
45
+ | `--params PATH` | parameters from a YAML/JSON mapping |
46
+ | `--run-dir DIR` | run directory (default `runs/<pipeline name>`, so re-running resumes) |
47
+
48
+ An agent parameter can be a config path (`--param judge=agents/judge.yaml`) or
49
+ inline JSON (`--param 'judge={"harness": "claude", "model": "opus"}'`).
50
+
51
+ ## `ghostlab status`
52
+
53
+ ```bash
54
+ ghostlab status runs/mcp-eval
55
+ ```
56
+
57
+ Prints done, cached, and failed counts per stage and lists the failed items.
58
+ Exits `1` when the run is incomplete or has failures.
@@ -0,0 +1,62 @@
1
+ # Getting started
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ pip install ghostlab # Python 3.10+
7
+ ```
8
+
9
+ You also need the CLI of at least one harness (`claude`, `codex`, `copilot`,
10
+ or `opencode`). For sandboxed runs, install
11
+ [NVIDIA OpenShell](https://docs.nvidia.com/openshell/latest/) and check it
12
+ with `openshell status`.
13
+
14
+ ## Run one agent
15
+
16
+ ```yaml
17
+ # helper.yaml
18
+ harness: claude
19
+ model: sonnet
20
+ instructions: Be concise.
21
+ sandbox: local # or {backend: openshell, providers: [anthropic]}
22
+ ```
23
+
24
+ ```bash
25
+ ghostlab exec --agent helper.yaml "What is 17 * 23? Reply with the number only."
26
+ ```
27
+
28
+ The answer goes to stdout. The full trace (prompt, raw stream, stderr,
29
+ `result.json`) is kept in `runs/<timestamp>-helper/turn-1/`.
30
+
31
+ The same from Python:
32
+
33
+ ```python
34
+ from ghostlab import Agent
35
+
36
+ helper = Agent.load("helper.yaml")
37
+ result = helper.run("What is 17 * 23?")
38
+ print(result.status, result.answer)
39
+ ```
40
+
41
+ ## Run one pipeline
42
+
43
+ ```python
44
+ # label.py
45
+ from ghostlab import Agent, Pipeline, stage
46
+
47
+ @stage(concurrency=8, retries=2)
48
+ def label(ctx, ticket):
49
+ reply = ctx.run(ctx.agent("labeler"),
50
+ f"Label as bug, billing, or other. JSON {{\"label\": ...}}.\n\n{ticket['text']}")
51
+ return {**ticket, **reply.json()}
52
+
53
+ pipeline = Pipeline("label", [label], params={"labeler": Agent.load("helper.yaml")})
54
+ ```
55
+
56
+ ```bash
57
+ ghostlab run label.py --input tickets.jsonl # writes runs/label/results.jsonl
58
+ ghostlab run label.py # resume: done items are skipped
59
+ ghostlab status runs/label
60
+ ```
61
+
62
+ Next: the [agent reference](agents.md) and the [pipeline guide](pipelines.md).
@@ -0,0 +1,26 @@
1
+ # Ghostlab
2
+
3
+ Ghostlab runs coding agents headlessly and composes them into checkpointed,
4
+ resumable jobs.
5
+
6
+ - **One agent definition, any harness.** Claude Code, Codex, GitHub Copilot
7
+ CLI, OpenCode, or any CLI you point it at. Instructions, skills, MCP servers,
8
+ and a workspace mean the same thing in each.
9
+ - **Sandboxed by default.** The agent CLI, its stdio MCPs, and a copy of its
10
+ workspace run inside an NVIDIA OpenShell sandbox that is deleted afterwards.
11
+ Running on the host (`sandbox: local`) must be asked for.
12
+ - **Pipelines in plain Python.** Stages map, fan out, or gather items; each
13
+ stage calls agents through a context that caches every call. Re-running a
14
+ pipeline resumes it.
15
+
16
+ Ghostlab is the infrastructure layer. Evaluations, labeling runs, test
17
+ generation, and migrations are pipelines you write on top of it. See
18
+ [the MCP evaluation example](https://github.com/sajjadGG/Ghostlab/blob/main/examples/mcp-eval/pipeline.py).
19
+
20
+ | Page | Read it for |
21
+ | --- | --- |
22
+ | [Getting started](getting-started.md) | install, run one agent, run one pipeline |
23
+ | [Agents](agents.md) | the agent config, every harness's options, the sandbox |
24
+ | [Pipelines](pipelines.md) | stages, the context, checkpointing and resume |
25
+ | [CLI](cli.md) | `ghostlab exec`, `run`, `status` |
26
+ | [Publishing](publishing.md) | releasing the package and this wiki |