agent-team 0.5.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 (80) hide show
  1. agent_team/__init__.py +3 -0
  2. agent_team/agent.py +109 -0
  3. agent_team/budget.py +56 -0
  4. agent_team/cli.py +664 -0
  5. agent_team/config.py +239 -0
  6. agent_team/config_io.py +229 -0
  7. agent_team/context.py +126 -0
  8. agent_team/dashboard/__init__.py +1 -0
  9. agent_team/dashboard/server.py +410 -0
  10. agent_team/dashboard/static/__init__.py +0 -0
  11. agent_team/dashboard/static/activity.js +110 -0
  12. agent_team/dashboard/static/app.css +214 -0
  13. agent_team/dashboard/static/app.js +351 -0
  14. agent_team/dashboard/static/edit.js +312 -0
  15. agent_team/dashboard/static/index.html +55 -0
  16. agent_team/dashboard/static/projects.js +53 -0
  17. agent_team/dashboard/static/runs.js +148 -0
  18. agent_team/dashboard/static/settings.js +117 -0
  19. agent_team/discovery.py +88 -0
  20. agent_team/estimate.py +60 -0
  21. agent_team/evals.py +186 -0
  22. agent_team/files.py +247 -0
  23. agent_team/gates.py +71 -0
  24. agent_team/github.py +152 -0
  25. agent_team/issues.py +101 -0
  26. agent_team/jobs.py +244 -0
  27. agent_team/keys.py +110 -0
  28. agent_team/logs.py +106 -0
  29. agent_team/memory.py +87 -0
  30. agent_team/metrics.py +102 -0
  31. agent_team/notify.py +65 -0
  32. agent_team/projects.py +83 -0
  33. agent_team/prompts/__init__.py +0 -0
  34. agent_team/prompts/builder.md +15 -0
  35. agent_team/prompts/devops.md +28 -0
  36. agent_team/prompts/discuss.md +29 -0
  37. agent_team/prompts/docs.md +12 -0
  38. agent_team/prompts/factcheck.md +8 -0
  39. agent_team/prompts/lead.md +33 -0
  40. agent_team/prompts/marketing.md +18 -0
  41. agent_team/prompts/member.md +10 -0
  42. agent_team/prompts/qa.md +17 -0
  43. agent_team/prompts/review_marketing.md +14 -0
  44. agent_team/prompts/review_pr.md +26 -0
  45. agent_team/prompts/security.md +20 -0
  46. agent_team/prompts/signoff.md +13 -0
  47. agent_team/providers/__init__.py +8 -0
  48. agent_team/providers/anthropic_provider.py +150 -0
  49. agent_team/providers/base.py +96 -0
  50. agent_team/providers/http_providers.py +238 -0
  51. agent_team/rank.py +67 -0
  52. agent_team/readiness.py +189 -0
  53. agent_team/review_tools.py +142 -0
  54. agent_team/routing.py +28 -0
  55. agent_team/sandbox.py +105 -0
  56. agent_team/schedule.py +182 -0
  57. agent_team/scoreboard.py +83 -0
  58. agent_team/screenshots.py +135 -0
  59. agent_team/secret_scan.py +89 -0
  60. agent_team/server.py +119 -0
  61. agent_team/skills.py +96 -0
  62. agent_team/templates.py +109 -0
  63. agent_team/tool_loop.py +58 -0
  64. agent_team/ui.py +59 -0
  65. agent_team/usage.py +136 -0
  66. agent_team/workflows/__init__.py +1 -0
  67. agent_team/workflows/autopilot.py +163 -0
  68. agent_team/workflows/build.py +305 -0
  69. agent_team/workflows/discuss.py +84 -0
  70. agent_team/workflows/fix.py +173 -0
  71. agent_team/workflows/init.py +253 -0
  72. agent_team/workflows/market.py +86 -0
  73. agent_team/workflows/setup.py +56 -0
  74. agent_team/workflows/ship.py +539 -0
  75. agent_team-0.5.0.dist-info/METADATA +180 -0
  76. agent_team-0.5.0.dist-info/RECORD +80 -0
  77. agent_team-0.5.0.dist-info/WHEEL +5 -0
  78. agent_team-0.5.0.dist-info/entry_points.txt +2 -0
  79. agent_team-0.5.0.dist-info/licenses/LICENSE +21 -0
  80. agent_team-0.5.0.dist-info/top_level.txt +1 -0
agent_team/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """agent-team: a configurable team of AI agents that works on a project through GitHub."""
2
+
3
+ __version__ = "0.5.0"
agent_team/agent.py ADDED
@@ -0,0 +1,109 @@
1
+ """A team member at runtime: its config, its provider, and an append-only conversation."""
2
+ from __future__ import annotations
3
+
4
+ from importlib import resources
5
+ from pathlib import Path
6
+
7
+ from . import budget, providers, ui, usage
8
+ from .config import AgentConfig, Team
9
+
10
+
11
+ def role_prompt(team: Team, agent: AgentConfig, name: str | None = None) -> str:
12
+ """The agent's prompt text: its own file if configured, else the packaged one for its role."""
13
+ if agent.prompt and name is None:
14
+ return (team.root / agent.prompt).read_text(encoding="utf-8")
15
+ fname = f"{name or agent.role}.md"
16
+ try:
17
+ return resources.files("agent_team.prompts").joinpath(fname).read_text(encoding="utf-8")
18
+ except FileNotFoundError:
19
+ return resources.files("agent_team.prompts").joinpath("member.md").read_text(encoding="utf-8")
20
+
21
+
22
+ def team_roster(team: Team) -> str:
23
+ lines = []
24
+ for a in team.agents.values():
25
+ where = team.providers[a.provider]
26
+ place = f"remote node {where.url}" if where.kind == "agent-http" else f"{where.kind} model {a.model}"
27
+ desc = f" {a.description}" if a.description else ""
28
+ owns = f" Owns: {', '.join(a.owns)}." if a.owns else ""
29
+ skills = f" Skills: {', '.join(_skill_names(team, a))}." if a.skills else ""
30
+ lines.append(f"- {a.name} ({a.role}, {place}).{desc}{owns}{skills}")
31
+ return "\n".join(lines)
32
+
33
+
34
+ class Agent:
35
+ def __init__(self, team: Team, cfg: AgentConfig, system: str, label_suffix: str = ""):
36
+ self.team, self.cfg, self.system = team, cfg, system
37
+ pcfg = team.providers[cfg.provider]
38
+ self.provider = providers.build(pcfg, team.secret(pcfg))
39
+ self.label = ui.label(cfg.name, cfg.role + label_suffix, cfg.color)
40
+ self.messages: list[dict] = [] # append-only, so provider-side reasoning stays valid
41
+
42
+ @property
43
+ def name(self) -> str:
44
+ return self.cfg.name
45
+
46
+ def turn(self, content) -> str:
47
+ if not self.provider.supports_images and not isinstance(content, str):
48
+ content = providers.base.to_text(content)
49
+ budget.check(self.team)
50
+ self.messages.append({"role": "user", "content": content})
51
+ reply = self.provider.complete(self.system, self.messages, model=self.cfg.model,
52
+ effort=self.cfg.effort, label=self.label)
53
+ self.messages.append({"role": "assistant", "content": reply.content})
54
+ # A remote node reports the model it really ran, so its member is priced correctly.
55
+ u = dict(reply.usage or {})
56
+ kind, model = u.pop("kind", "") or self.provider.kind, u.pop("model", "") or self.cfg.model
57
+ usage.record(self.team.root, self.cfg.name, kind, model, u)
58
+ return reply.text
59
+
60
+
61
+ def _skill_names(team: Team, cfg: AgentConfig) -> list[str]:
62
+ from . import skills
63
+ names = []
64
+ for entry in cfg.skills:
65
+ try:
66
+ names.append(skills.load_one(team, entry).name)
67
+ except Exception:
68
+ names.append(entry)
69
+ return names
70
+
71
+
72
+ def make(team: Team, cfg: AgentConfig, prompt_name: str | None = None, label_suffix: str = "",
73
+ **fields) -> Agent:
74
+ """Build an agent whose system prompt is its role prompt filled with team and project facts."""
75
+ template = role_prompt(team, cfg, prompt_name)
76
+ values = {
77
+ "name": cfg.name, "role": cfg.role, "project": team.project.name, "repo": team.project.repo,
78
+ "roster": team_roster(team), "owns": ", ".join(cfg.owns) or "nothing",
79
+ "guarantees": "\n".join(f"- {g}" for g in team.project.guarantees) or "- (none listed)",
80
+ "test_command": team.project.test_command or "(none configured)",
81
+ **fields,
82
+ }
83
+ system = _fill(template, values)
84
+ from . import memory
85
+ learned = memory.for_prompt(team.root)
86
+ if learned:
87
+ system = f"{system.rstrip()}\n\n{learned}"
88
+ if cfg.skills:
89
+ from . import skills
90
+ system = f"{system.rstrip()}\n\n{skills.render(skills.load(team, cfg))}"
91
+ return Agent(team, cfg, system, label_suffix)
92
+
93
+
94
+ def _fill(template: str, values: dict) -> str:
95
+ """str.format that leaves unknown {placeholders} and literal braces alone."""
96
+ class Keep(dict):
97
+ def __missing__(self, key):
98
+ return "{" + key + "}"
99
+ try:
100
+ return template.format_map(Keep(values))
101
+ except (ValueError, IndexError):
102
+ out = template
103
+ for k, v in values.items():
104
+ out = out.replace("{" + k + "}", str(v))
105
+ return out
106
+
107
+
108
+ def prompt_path(team: Team, agent: AgentConfig) -> Path | None:
109
+ return team.root / agent.prompt if agent.prompt else None
agent_team/budget.py ADDED
@@ -0,0 +1,56 @@
1
+ """Spend limits: stop a run before it costs more than `[limits]` allows.
2
+
3
+ [limits]
4
+ per_run = 5.0 # US dollars for one workflow run
5
+ per_day = 20.0 # US dollars per UTC day, across every run in this project
6
+
7
+ Checked before every model call, from the estimates in `.team/usage.jsonl`. A call already in
8
+ flight finishes, so a run can go over by at most one reply.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from . import usage
13
+ from .config import Team
14
+
15
+
16
+ class SpendLimitError(RuntimeError):
17
+ pass
18
+
19
+
20
+ def spent(team: Team, run: str | None = None) -> dict:
21
+ """Estimated dollars spent in this run and today (UTC)."""
22
+ run = usage.current_run() if run is None else run
23
+ today = usage.now()[:10]
24
+ out = {"run": 0.0, "today": 0.0}
25
+ for e in usage.load(team.root):
26
+ cost = usage.price_for(e.get("model", ""), e.get("kind", ""), team.pricing).cost(e)
27
+ if run and e.get("run") == run:
28
+ out["run"] += cost
29
+ if (e.get("at") or "")[:10] == today:
30
+ out["today"] += cost
31
+ return {k: round(v, 4) for k, v in out.items()}
32
+
33
+
34
+ def milestone_spent(team: Team, milestone: str) -> float:
35
+ return round(sum(usage.price_for(e.get("model", ""), e.get("kind", ""), team.pricing).cost(e)
36
+ for e in usage.load(team.root) if e.get("milestone") == milestone), 4)
37
+
38
+
39
+ def check(team: Team) -> None:
40
+ limits = team.limits
41
+ milestone = usage.current_milestone()
42
+ cap = limits.milestones.get(milestone) if milestone else None
43
+ if cap:
44
+ spent_m = milestone_spent(team, milestone)
45
+ if spent_m >= cap:
46
+ raise SpendLimitError(f"milestone {milestone} has spent an estimated ${spent_m:.2f}, reaching its "
47
+ f"[limits.milestones] budget of ${cap:.2f}")
48
+ if not (limits.per_run or limits.per_day):
49
+ return
50
+ s = spent(team)
51
+ if limits.per_run and s["run"] >= limits.per_run:
52
+ raise SpendLimitError(f"this run has spent an estimated ${s['run']:.2f}, reaching the "
53
+ f"[limits].per_run limit of ${limits.per_run:.2f}")
54
+ if limits.per_day and s["today"] >= limits.per_day:
55
+ raise SpendLimitError(f"this project has spent an estimated ${s['today']:.2f} today (UTC), reaching "
56
+ f"the [limits].per_day limit of ${limits.per_day:.2f}")