probiotic 0.1.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 (56) hide show
  1. probiotic-0.1.0/.gitignore +12 -0
  2. probiotic-0.1.0/PKG-INFO +78 -0
  3. probiotic-0.1.0/README.md +54 -0
  4. probiotic-0.1.0/hive/__init__.py +4 -0
  5. probiotic-0.1.0/hive/analyzer.py +78 -0
  6. probiotic-0.1.0/hive/config.py +25 -0
  7. probiotic-0.1.0/hive/distributor.py +14 -0
  8. probiotic-0.1.0/hive/models.py +229 -0
  9. probiotic-0.1.0/hive/registry.py +15 -0
  10. probiotic-0.1.0/hive/server.py +86 -0
  11. probiotic-0.1.0/pyproject.toml +46 -0
  12. probiotic-0.1.0/src/probiotic/__init__.py +6 -0
  13. probiotic-0.1.0/src/probiotic/cli.py +390 -0
  14. probiotic-0.1.0/src/probiotic/dna/__init__.py +4 -0
  15. probiotic-0.1.0/src/probiotic/dna/binary_finder.py +146 -0
  16. probiotic-0.1.0/src/probiotic/dna/config.py +113 -0
  17. probiotic-0.1.0/src/probiotic/dna/hive_client.py +119 -0
  18. probiotic-0.1.0/src/probiotic/dna/journal.py +177 -0
  19. probiotic-0.1.0/src/probiotic/dna/lock.py +54 -0
  20. probiotic-0.1.0/src/probiotic/dna/prompts.py +167 -0
  21. probiotic-0.1.0/src/probiotic/dna/runtime.py +266 -0
  22. probiotic-0.1.0/src/probiotic/dna/scheduler.py +162 -0
  23. probiotic-0.1.0/src/probiotic/dna/tickets.py +72 -0
  24. probiotic-0.1.0/src/probiotic/dna/tools/__init__.py +2 -0
  25. probiotic-0.1.0/src/probiotic/dna/tools/usage.py +215 -0
  26. probiotic-0.1.0/src/probiotic/templates/__init__.py +2 -0
  27. probiotic-0.1.0/src/probiotic/templates/_base/SOUL.md.j2 +7 -0
  28. probiotic-0.1.0/src/probiotic/templates/_base/agent.md.j2 +25 -0
  29. probiotic-0.1.0/src/probiotic/templates/_base/cell.toml.default +13 -0
  30. probiotic-0.1.0/src/probiotic/templates/commit/SOUL.md +4 -0
  31. probiotic-0.1.0/src/probiotic/templates/commit/agent.md.j2 +22 -0
  32. probiotic-0.1.0/src/probiotic/templates/commit/cell.toml.default +13 -0
  33. probiotic-0.1.0/src/probiotic/templates/documentation/SOUL.md +4 -0
  34. probiotic-0.1.0/src/probiotic/templates/documentation/agent.md.j2 +23 -0
  35. probiotic-0.1.0/src/probiotic/templates/documentation/cell.toml.default +13 -0
  36. probiotic-0.1.0/src/probiotic/templates/janitor/SOUL.md +7 -0
  37. probiotic-0.1.0/src/probiotic/templates/janitor/agent.md.j2 +35 -0
  38. probiotic-0.1.0/src/probiotic/templates/janitor/cell.toml.default +13 -0
  39. probiotic-0.1.0/src/probiotic/templates/manager/SOUL.md +8 -0
  40. probiotic-0.1.0/src/probiotic/templates/manager/agent.md.j2 +35 -0
  41. probiotic-0.1.0/src/probiotic/templates/manager/cell.toml.default +13 -0
  42. probiotic-0.1.0/src/probiotic/templates/progress_tracker/SOUL.md +4 -0
  43. probiotic-0.1.0/src/probiotic/templates/progress_tracker/agent.md.j2 +29 -0
  44. probiotic-0.1.0/src/probiotic/templates/progress_tracker/cell.toml.default +13 -0
  45. probiotic-0.1.0/src/probiotic/templates/researcher/SOUL.md +4 -0
  46. probiotic-0.1.0/src/probiotic/templates/researcher/agent.md.j2 +42 -0
  47. probiotic-0.1.0/src/probiotic/templates/researcher/cell.toml.default +13 -0
  48. probiotic-0.1.0/tests/__init__.py +0 -0
  49. probiotic-0.1.0/tests/test_probiotic_cli.py +63 -0
  50. probiotic-0.1.0/tests/test_probiotic_hive_client.py +60 -0
  51. probiotic-0.1.0/tests/test_probiotic_hive_server.py +50 -0
  52. probiotic-0.1.0/tests/test_probiotic_journal.py +58 -0
  53. probiotic-0.1.0/tests/test_probiotic_prompts.py +53 -0
  54. probiotic-0.1.0/tests/test_probiotic_runtime.py +91 -0
  55. probiotic-0.1.0/tests/test_probiotic_scheduler.py +41 -0
  56. probiotic-0.1.0/tests/test_probiotic_tickets.py +30 -0
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.lock
9
+ *.log
10
+ .env
11
+ .venv/
12
+ .pytest_cache/
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: probiotic
3
+ Version: 0.1.0
4
+ Summary: Autonomous self-improving agent framework for code repositories
5
+ Project-URL: Homepage, https://github.com/penthoy/probiotic
6
+ Project-URL: Repository, https://github.com/penthoy/probiotic
7
+ Project-URL: Issues, https://github.com/penthoy/probiotic/issues
8
+ Keywords: agents,automation,cli,symbiont
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.11
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
19
+ Requires-Dist: pytest>=8.0; extra == 'dev'
20
+ Provides-Extra: hive-server
21
+ Requires-Dist: fastapi>=0.110; extra == 'hive-server'
22
+ Requires-Dist: uvicorn>=0.30; extra == 'hive-server'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Probiotic
26
+
27
+ Probiotic is a lightweight runtime for repository-native autonomous agents.
28
+
29
+ It provides:
30
+
31
+ - a DNA runtime that installs as a Python package
32
+ - repo-local cells under `symbiont/cells/`
33
+ - a nightly scheduler
34
+ - optional hive reporting and prompt improvement distribution
35
+
36
+ ## Status
37
+
38
+ This project is currently alpha.
39
+
40
+ The v2 loop implemented in this repo includes:
41
+
42
+ - prompt hashing based on shareable prompt text
43
+ - passive approval metrics from local run history
44
+ - hive prompt snapshot storage
45
+ - analyzer-generated prompt patches
46
+ - nightly `apply_updates()` before cell execution
47
+
48
+ ## Install
49
+
50
+ Until the package is published to PyPI, install it from GitHub or from a built wheel:
51
+
52
+ ```bash
53
+ pip install git+https://github.com/penthoy/probiotic.git
54
+ ```
55
+
56
+ or
57
+
58
+ ```bash
59
+ pip install probiotic-0.1.0-py3-none-any.whl
60
+ ```
61
+
62
+ ## CLI
63
+
64
+ ```bash
65
+ probiotic init
66
+ probiotic spawn janitor --template janitor
67
+ probiotic run-nightly
68
+ probiotic connect-hive http://<host>:8000 --api-key <key>
69
+ probiotic update-check
70
+ ```
71
+
72
+ ## Development
73
+
74
+ ```bash
75
+ uv sync --extra dev --extra hive-server
76
+ uv run pytest
77
+ uv build
78
+ ```
@@ -0,0 +1,54 @@
1
+ # Probiotic
2
+
3
+ Probiotic is a lightweight runtime for repository-native autonomous agents.
4
+
5
+ It provides:
6
+
7
+ - a DNA runtime that installs as a Python package
8
+ - repo-local cells under `symbiont/cells/`
9
+ - a nightly scheduler
10
+ - optional hive reporting and prompt improvement distribution
11
+
12
+ ## Status
13
+
14
+ This project is currently alpha.
15
+
16
+ The v2 loop implemented in this repo includes:
17
+
18
+ - prompt hashing based on shareable prompt text
19
+ - passive approval metrics from local run history
20
+ - hive prompt snapshot storage
21
+ - analyzer-generated prompt patches
22
+ - nightly `apply_updates()` before cell execution
23
+
24
+ ## Install
25
+
26
+ Until the package is published to PyPI, install it from GitHub or from a built wheel:
27
+
28
+ ```bash
29
+ pip install git+https://github.com/penthoy/probiotic.git
30
+ ```
31
+
32
+ or
33
+
34
+ ```bash
35
+ pip install probiotic-0.1.0-py3-none-any.whl
36
+ ```
37
+
38
+ ## CLI
39
+
40
+ ```bash
41
+ probiotic init
42
+ probiotic spawn janitor --template janitor
43
+ probiotic run-nightly
44
+ probiotic connect-hive http://<host>:8000 --api-key <key>
45
+ probiotic update-check
46
+ ```
47
+
48
+ ## Development
49
+
50
+ ```bash
51
+ uv sync --extra dev --extra hive-server
52
+ uv run pytest
53
+ uv build
54
+ ```
@@ -0,0 +1,4 @@
1
+ """Optional Probiotic hive server package."""
2
+
3
+ from __future__ import annotations
4
+
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from collections import defaultdict
5
+
6
+ from .models import HiveStore, RunRecord, VerifiedImprovement
7
+
8
+
9
+ def _confidence(delta: float, repo_count: int) -> float:
10
+ return min(0.99, max(0.0, 0.65 + delta + repo_count * 0.05))
11
+
12
+
13
+ def find_verified_improvements(
14
+ records: list[RunRecord],
15
+ *,
16
+ prompt_lookup: Callable[[str, str], str | None] | None = None,
17
+ min_repos: int = 3,
18
+ min_delta: float = 0.10,
19
+ min_confidence: float = 0.80,
20
+ ) -> list[VerifiedImprovement]:
21
+ by_hash: dict[str, list[RunRecord]] = defaultdict(list)
22
+ for record in records:
23
+ if record.approval_rate_30d is not None:
24
+ by_hash[record.prompt_hash].append(record)
25
+
26
+ improvements: list[VerifiedImprovement] = []
27
+ for prompt_hash, grouped in by_hash.items():
28
+ repos = {record.instance_id for record in grouped}
29
+ if len(repos) < min_repos:
30
+ continue
31
+ previous_rates = [
32
+ float(record.metrics["previous_approval_rate_30d"])
33
+ for record in grouped
34
+ if "previous_approval_rate_30d" in record.metrics
35
+ ]
36
+ if not previous_rates:
37
+ continue
38
+ previous = sum(previous_rates) / len(previous_rates)
39
+ current = sum(float(record.approval_rate_30d or 0.0) for record in grouped) / len(grouped)
40
+ delta = current - previous
41
+ confidence = _confidence(delta, len(repos))
42
+ if delta >= min_delta and confidence >= min_confidence:
43
+ sample = grouped[0]
44
+ patch = prompt_lookup(prompt_hash, sample.agent) if prompt_lookup is not None else ""
45
+ if prompt_lookup is not None and not patch:
46
+ continue
47
+ improvements.append(
48
+ VerifiedImprovement(
49
+ agent_template=sample.agent,
50
+ prompt_hash=prompt_hash,
51
+ change_type="prompt_patch",
52
+ description=f"{sample.agent} prompt correlated with {delta:.0%} approval-rate lift across {len(repos)} repos",
53
+ patch=patch,
54
+ confidence=confidence,
55
+ verified_repos=len(repos),
56
+ )
57
+ )
58
+ return improvements
59
+
60
+
61
+ def analyze_and_store(
62
+ store: HiveStore,
63
+ *,
64
+ min_repos: int = 3,
65
+ min_delta: float = 0.10,
66
+ min_confidence: float = 0.80,
67
+ ) -> list[VerifiedImprovement]:
68
+ improvements = find_verified_improvements(
69
+ store.reports(),
70
+ prompt_lookup=lambda prompt_hash, agent: store.get_prompt_by_hash(prompt_hash, agent=agent),
71
+ min_repos=min_repos,
72
+ min_delta=min_delta,
73
+ min_confidence=min_confidence,
74
+ )
75
+ for improvement in improvements:
76
+ store.save_improvement(improvement)
77
+ return improvements
78
+
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class HiveServerConfig:
10
+ database_path: Path = Path("hive.sqlite3")
11
+ api_key: str = ""
12
+ min_repos: int = 2
13
+ min_delta: float = 0.10
14
+ min_confidence: float = 0.80
15
+
16
+ @classmethod
17
+ def from_env(cls) -> "HiveServerConfig":
18
+ return cls(
19
+ database_path=Path(os.environ.get("PROBIOTIC_HIVE_DB", "hive.sqlite3")),
20
+ api_key=os.environ.get("PROBIOTIC_HIVE_API_KEY", ""),
21
+ min_repos=int(os.environ.get("PROBIOTIC_HIVE_MIN_REPOS", "2")),
22
+ min_delta=float(os.environ.get("PROBIOTIC_HIVE_MIN_DELTA", "0.10")),
23
+ min_confidence=float(os.environ.get("PROBIOTIC_HIVE_MIN_CONFIDENCE", "0.80")),
24
+ )
25
+
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict
4
+
5
+ from .models import HiveStore
6
+
7
+
8
+ def updates_for_instance(store: HiveStore, *, instance_id: str) -> dict:
9
+ return {
10
+ "improvements": [asdict(improvement) for improvement in store.improvements()],
11
+ "new_templates": [],
12
+ "instance_id": instance_id,
13
+ }
14
+
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sqlite3
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class Instance:
13
+ instance_id: str
14
+ repo_name: str
15
+ last_seen: str
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class RunRecord:
20
+ instance_id: str
21
+ repo_name: str
22
+ agent: str
23
+ run_date: str
24
+ outcome: str
25
+ prompt_hash: str
26
+ approval_rate_30d: float | None = None
27
+ metrics: dict[str, Any] = field(default_factory=dict)
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class VerifiedImprovement:
32
+ agent_template: str
33
+ prompt_hash: str
34
+ change_type: str
35
+ description: str
36
+ patch: str
37
+ confidence: float
38
+ verified_repos: int
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class PromptSnapshot:
43
+ prompt_hash: str
44
+ agent: str
45
+ content: str
46
+ first_seen: str
47
+
48
+
49
+ class HiveStore:
50
+ def __init__(self, path: Path | str = "hive.sqlite3") -> None:
51
+ self.path = Path(path)
52
+ self.init_db()
53
+
54
+ def connect(self) -> sqlite3.Connection:
55
+ con = sqlite3.connect(str(self.path))
56
+ con.row_factory = sqlite3.Row
57
+ return con
58
+
59
+ def init_db(self) -> None:
60
+ self.path.parent.mkdir(parents=True, exist_ok=True)
61
+ with self.connect() as con:
62
+ con.execute(
63
+ """
64
+ CREATE TABLE IF NOT EXISTS instances (
65
+ instance_id TEXT PRIMARY KEY,
66
+ repo_name TEXT NOT NULL,
67
+ last_seen TEXT NOT NULL
68
+ )
69
+ """
70
+ )
71
+ con.execute(
72
+ """
73
+ CREATE TABLE IF NOT EXISTS run_records (
74
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
75
+ instance_id TEXT NOT NULL,
76
+ repo_name TEXT NOT NULL,
77
+ agent TEXT NOT NULL,
78
+ run_date TEXT NOT NULL,
79
+ outcome TEXT NOT NULL,
80
+ prompt_hash TEXT NOT NULL,
81
+ approval_rate_30d REAL,
82
+ metrics_json TEXT NOT NULL
83
+ )
84
+ """
85
+ )
86
+ con.execute(
87
+ """
88
+ CREATE TABLE IF NOT EXISTS verified_improvements (
89
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
90
+ agent_template TEXT NOT NULL,
91
+ prompt_hash TEXT NOT NULL UNIQUE,
92
+ change_type TEXT NOT NULL,
93
+ description TEXT NOT NULL,
94
+ patch TEXT NOT NULL,
95
+ confidence REAL NOT NULL,
96
+ verified_repos INTEGER NOT NULL
97
+ )
98
+ """
99
+ )
100
+ con.execute(
101
+ """
102
+ CREATE TABLE IF NOT EXISTS prompt_snapshots (
103
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
104
+ prompt_hash TEXT NOT NULL,
105
+ agent TEXT NOT NULL,
106
+ content TEXT NOT NULL,
107
+ first_seen TEXT NOT NULL,
108
+ UNIQUE(prompt_hash, agent)
109
+ )
110
+ """
111
+ )
112
+
113
+ def save_report(self, report: RunRecord) -> None:
114
+ now = datetime.now().astimezone().isoformat(timespec="seconds")
115
+ with self.connect() as con:
116
+ con.execute(
117
+ """
118
+ INSERT INTO instances(instance_id, repo_name, last_seen)
119
+ VALUES (?, ?, ?)
120
+ ON CONFLICT(instance_id) DO UPDATE SET repo_name=excluded.repo_name, last_seen=excluded.last_seen
121
+ """,
122
+ (report.instance_id, report.repo_name, now),
123
+ )
124
+ con.execute(
125
+ """
126
+ INSERT INTO run_records(
127
+ instance_id, repo_name, agent, run_date, outcome,
128
+ prompt_hash, approval_rate_30d, metrics_json
129
+ )
130
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
131
+ """,
132
+ (
133
+ report.instance_id,
134
+ report.repo_name,
135
+ report.agent,
136
+ report.run_date,
137
+ report.outcome,
138
+ report.prompt_hash,
139
+ report.approval_rate_30d,
140
+ json.dumps(report.metrics),
141
+ ),
142
+ )
143
+
144
+ def reports(self) -> list[RunRecord]:
145
+ with self.connect() as con:
146
+ rows = con.execute("SELECT * FROM run_records ORDER BY id").fetchall()
147
+ return [
148
+ RunRecord(
149
+ instance_id=row["instance_id"],
150
+ repo_name=row["repo_name"],
151
+ agent=row["agent"],
152
+ run_date=row["run_date"],
153
+ outcome=row["outcome"],
154
+ prompt_hash=row["prompt_hash"],
155
+ approval_rate_30d=row["approval_rate_30d"],
156
+ metrics=json.loads(row["metrics_json"]),
157
+ )
158
+ for row in rows
159
+ ]
160
+
161
+ def save_prompt_snapshot(self, *, prompt_hash: str, agent: str, content: str) -> None:
162
+ now = datetime.now().astimezone().isoformat(timespec="seconds")
163
+ with self.connect() as con:
164
+ con.execute(
165
+ """
166
+ INSERT INTO prompt_snapshots(prompt_hash, agent, content, first_seen)
167
+ VALUES (?, ?, ?, ?)
168
+ ON CONFLICT(prompt_hash, agent) DO NOTHING
169
+ """,
170
+ (prompt_hash, agent, content, now),
171
+ )
172
+
173
+ def get_prompt_by_hash(self, prompt_hash: str, *, agent: str | None = None) -> str | None:
174
+ query = "SELECT content FROM prompt_snapshots WHERE prompt_hash = ?"
175
+ params: list[Any] = [prompt_hash]
176
+ if agent is not None:
177
+ query += " AND agent = ?"
178
+ params.append(agent)
179
+ query += " ORDER BY first_seen LIMIT 1"
180
+ with self.connect() as con:
181
+ row = con.execute(query, params).fetchone()
182
+ if row is None:
183
+ return None
184
+ return str(row["content"])
185
+
186
+ def save_improvement(self, improvement: VerifiedImprovement) -> None:
187
+ with self.connect() as con:
188
+ con.execute(
189
+ """
190
+ INSERT INTO verified_improvements(
191
+ agent_template, prompt_hash, change_type, description,
192
+ patch, confidence, verified_repos
193
+ )
194
+ VALUES (?, ?, ?, ?, ?, ?, ?)
195
+ ON CONFLICT(prompt_hash) DO UPDATE SET
196
+ agent_template=excluded.agent_template,
197
+ change_type=excluded.change_type,
198
+ description=excluded.description,
199
+ patch=excluded.patch,
200
+ confidence=excluded.confidence,
201
+ verified_repos=excluded.verified_repos
202
+ """,
203
+ (
204
+ improvement.agent_template,
205
+ improvement.prompt_hash,
206
+ improvement.change_type,
207
+ improvement.description,
208
+ improvement.patch,
209
+ improvement.confidence,
210
+ improvement.verified_repos,
211
+ ),
212
+ )
213
+
214
+ def improvements(self) -> list[VerifiedImprovement]:
215
+ with self.connect() as con:
216
+ rows = con.execute("SELECT * FROM verified_improvements ORDER BY id").fetchall()
217
+ return [
218
+ VerifiedImprovement(
219
+ agent_template=row["agent_template"],
220
+ prompt_hash=row["prompt_hash"],
221
+ change_type=row["change_type"],
222
+ description=row["description"],
223
+ patch=row["patch"],
224
+ confidence=row["confidence"],
225
+ verified_repos=row["verified_repos"],
226
+ )
227
+ for row in rows
228
+ ]
229
+
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from .models import HiveStore, RunRecord
4
+
5
+
6
+ class Registry:
7
+ def __init__(self, store: HiveStore) -> None:
8
+ self.store = store
9
+
10
+ def record_report(self, report: RunRecord, *, prompt_text: str | None = None) -> dict[str, str]:
11
+ self.store.save_report(report)
12
+ if prompt_text and prompt_text.strip():
13
+ self.store.save_prompt_snapshot(prompt_hash=report.prompt_hash, agent=report.agent, content=prompt_text)
14
+ return {"status": "ok"}
15
+
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict
4
+ from typing import Any
5
+
6
+ from .analyzer import analyze_and_store
7
+ from .config import HiveServerConfig
8
+ from .distributor import updates_for_instance
9
+ from .models import HiveStore, RunRecord
10
+ from .registry import Registry
11
+
12
+ try: # pragma: no cover - exercised only when optional dependency is installed
13
+ from fastapi import Depends, FastAPI, Header, HTTPException
14
+ except ImportError: # pragma: no cover
15
+ Depends = FastAPI = Header = HTTPException = None # type: ignore[assignment]
16
+
17
+
18
+ config = HiveServerConfig.from_env()
19
+ store = HiveStore(config.database_path)
20
+ registry = Registry(store)
21
+
22
+
23
+ def _require_fastapi() -> None:
24
+ if FastAPI is None:
25
+ raise RuntimeError("Install probiotic[hive-server] to run the hive server")
26
+
27
+
28
+ def _auth(authorization: str | None = None) -> None:
29
+ if not config.api_key:
30
+ return
31
+ expected = f"Bearer {config.api_key}"
32
+ if authorization != expected:
33
+ raise HTTPException(status_code=401, detail="invalid hive API key")
34
+
35
+
36
+ def create_app():
37
+ _require_fastapi()
38
+ app = FastAPI(title="Probiotic Hive")
39
+
40
+ @app.get("/health")
41
+ def health() -> dict[str, str]:
42
+ return {"status": "ok"}
43
+
44
+ @app.post("/report")
45
+ def report(payload: dict[str, Any], authorization: str | None = Header(default=None)) -> dict[str, str]:
46
+ _auth(authorization)
47
+ record = RunRecord(
48
+ instance_id=str(payload["instance_id"]),
49
+ repo_name=str(payload["repo_name"]),
50
+ agent=str(payload["agent"]),
51
+ run_date=str(payload["run_date"]),
52
+ outcome=str(payload["outcome"]),
53
+ prompt_hash=str(payload["prompt_hash"]),
54
+ approval_rate_30d=payload.get("approval_rate_30d"),
55
+ metrics=dict(payload.get("metrics", {})),
56
+ )
57
+ result = registry.record_report(record, prompt_text=payload.get("prompt_text"))
58
+ analyze_and_store(
59
+ store,
60
+ min_repos=config.min_repos,
61
+ min_delta=config.min_delta,
62
+ min_confidence=config.min_confidence,
63
+ )
64
+ return result
65
+
66
+ @app.get("/updates")
67
+ def updates(instance_id: str, since: str | None = None, authorization: str | None = Header(default=None)) -> dict:
68
+ _auth(authorization)
69
+ return updates_for_instance(store, instance_id=instance_id)
70
+
71
+ @app.post("/analyze")
72
+ def analyze(authorization: str | None = Header(default=None)) -> dict:
73
+ _auth(authorization)
74
+ improvements = analyze_and_store(
75
+ store,
76
+ min_repos=config.min_repos,
77
+ min_delta=config.min_delta,
78
+ min_confidence=config.min_confidence,
79
+ )
80
+ return {"improvements": [asdict(item) for item in improvements]}
81
+
82
+ return app
83
+
84
+
85
+ app = create_app() if FastAPI is not None else None
86
+
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "probiotic"
3
+ version = "0.1.0"
4
+ description = "Autonomous self-improving agent framework for code repositories"
5
+ requires-python = ">=3.11"
6
+ readme = "README.md"
7
+ keywords = ["agents", "automation", "cli", "symbiont"]
8
+ classifiers = [
9
+ "Development Status :: 3 - Alpha",
10
+ "Environment :: Console",
11
+ "Intended Audience :: Developers",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Topic :: Software Development :: Libraries",
16
+ ]
17
+ dependencies = []
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/penthoy/probiotic"
21
+ Repository = "https://github.com/penthoy/probiotic"
22
+ Issues = "https://github.com/penthoy/probiotic/issues"
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=8.0",
27
+ "pytest-asyncio>=0.24",
28
+ ]
29
+ hive-server = [
30
+ "fastapi>=0.110",
31
+ "uvicorn>=0.30",
32
+ ]
33
+
34
+ [project.scripts]
35
+ probiotic = "probiotic.cli:main"
36
+
37
+ [build-system]
38
+ requires = ["hatchling"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/probiotic"]
43
+
44
+ [tool.pytest.ini_options]
45
+ asyncio_mode = "auto"
46
+ testpaths = ["tests"]
@@ -0,0 +1,6 @@
1
+ """Probiotic: autonomous symbiont runtime for code repositories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"
6
+