agent2you 1.0.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 (41) hide show
  1. agent2you-1.0.0/LICENSE +21 -0
  2. agent2you-1.0.0/PKG-INFO +163 -0
  3. agent2you-1.0.0/README.md +136 -0
  4. agent2you-1.0.0/a2y/__init__.py +3 -0
  5. agent2you-1.0.0/a2y/agents_cmd.py +184 -0
  6. agent2you-1.0.0/a2y/cli.py +264 -0
  7. agent2you-1.0.0/a2y/doctor.py +116 -0
  8. agent2you-1.0.0/a2y/image/agent.dockerfile +215 -0
  9. agent2you-1.0.0/a2y/image/apply-memory-profile.py +123 -0
  10. agent2you-1.0.0/a2y/image/bashrc +39 -0
  11. agent2you-1.0.0/a2y/image/entrypoint.sh +282 -0
  12. agent2you-1.0.0/a2y/image/fleet-roster.py +124 -0
  13. agent2you-1.0.0/a2y/image/plugins/_.hint +63 -0
  14. agent2you-1.0.0/a2y/image/plugins/conversation-key/__init__.py +61 -0
  15. agent2you-1.0.0/a2y/image/plugins/conversation-key/plugin.yaml +8 -0
  16. agent2you-1.0.0/a2y/image/plugins/hermes_otel/config.yaml +62 -0
  17. agent2you-1.0.0/a2y/image/plugins/mention-on-edit/__init__.py +301 -0
  18. agent2you-1.0.0/a2y/image/plugins/mention-on-edit/plugin.yaml +7 -0
  19. agent2you-1.0.0/a2y/image/plugins/reasoning-live/__init__.py +435 -0
  20. agent2you-1.0.0/a2y/image/plugins/reasoning-live/plugin.yaml +9 -0
  21. agent2you-1.0.0/a2y/image/plugins/steer-into-turn/__init__.py +419 -0
  22. agent2you-1.0.0/a2y/image/plugins/steer-into-turn/plugin.yaml +9 -0
  23. agent2you-1.0.0/a2y/image/plugins/trace-to-card/__init__.py +329 -0
  24. agent2you-1.0.0/a2y/image/plugins/trace-to-card/plugin.yaml +12 -0
  25. agent2you-1.0.0/a2y/image/plugins/untagged-routing/__init__.py +329 -0
  26. agent2you-1.0.0/a2y/image/plugins/untagged-routing/plugin.yaml +12 -0
  27. agent2you-1.0.0/a2y/image/plugins/untagged-routing/policy.py +160 -0
  28. agent2you-1.0.0/a2y/image/supervisord.conf +83 -0
  29. agent2you-1.0.0/a2y/manifest.py +278 -0
  30. agent2you-1.0.0/a2y/render.py +633 -0
  31. agent2you-1.0.0/a2y/scaffold.py +159 -0
  32. agent2you-1.0.0/agent2you.egg-info/PKG-INFO +163 -0
  33. agent2you-1.0.0/agent2you.egg-info/SOURCES.txt +39 -0
  34. agent2you-1.0.0/agent2you.egg-info/dependency_links.txt +1 -0
  35. agent2you-1.0.0/agent2you.egg-info/entry_points.txt +2 -0
  36. agent2you-1.0.0/agent2you.egg-info/requires.txt +1 -0
  37. agent2you-1.0.0/agent2you.egg-info/top_level.txt +1 -0
  38. agent2you-1.0.0/pyproject.toml +47 -0
  39. agent2you-1.0.0/setup.cfg +4 -0
  40. agent2you-1.0.0/tests/test_agent_add.py +60 -0
  41. agent2you-1.0.0/tests/test_render.py +130 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neprel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent2you
3
+ Version: 1.0.0
4
+ Summary: Deploy a team of chat-resident AI agents from declarative manifests: Hermes + litellm + acp2api + subscription coding CLIs, one container per agent.
5
+ Author: neprel
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/neprel/agent2you
8
+ Project-URL: Repository, https://github.com/neprel/agent2you
9
+ Project-URL: Issues, https://github.com/neprel/agent2you/issues
10
+ Keywords: agents,ai,hermes,acp,mattermost,docker,fleet,claude-code,codex
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Build Tools
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: pyyaml>=6.0
26
+ Dynamic: license-file
27
+
28
+ # agent2you
29
+
30
+ Deploy a **team of chat-resident AI agents** from declarative manifests: each
31
+ agent is one container holding Hermes (the employee), litellm (the failover
32
+ chain), acp2api (an OpenAI endpoint over ACP) and the subscription coding CLIs
33
+ (Claude Code, Codex, opencode, cline) that are its brain and hands.
34
+
35
+ The design was extracted from a production fleet that runs infrastructure,
36
+ product and PM agents as colleagues in Mattermost channels. Every decision in
37
+ this pack — one container per agent, no provider API keys, chat as the only
38
+ inter-agent surface, generated-not-hand-written configs — was measured there
39
+ first.
40
+
41
+ ## What an agent is
42
+
43
+ ```
44
+ chat platform (Mattermost / Telegram / Slack / ...)
45
+ │ @mention
46
+ ┌────┴─────────────────────────────────────────┐
47
+ │ hermes presence, sessions, memory, cron │
48
+ │ litellm failover: next brain on 429 │ one
49
+ │ acp2api OpenAI API ⇄ ACP, per-thread │ container
50
+ │ session continuity │ per agent
51
+ │ claude / codex / opencode / cline │
52
+ │ each spends its own SUBSCRIPTION │
53
+ └───────────────────────────────────────────────┘
54
+ ```
55
+
56
+ No provider API key exists anywhere in a default deployment: the CLIs log in to
57
+ their own subscriptions, which is the legal basis of the arrangement — and the
58
+ reason nothing here can quietly bill an API account.
59
+
60
+ Nothing is shared between agents except memory (optional, bank-per-agent) and
61
+ the chat workspace. Two agents cannot interfere with each other by construction.
62
+
63
+ ## Quickstart
64
+
65
+ ```bash
66
+ uv tool install agent2you # or: pipx install agent2you
67
+
68
+ a2y init myfleet && cd myfleet # a self-contained fleet workspace
69
+ $EDITOR fleet.yaml agents/ana/ # who exists, what platform, what memory
70
+ a2y render # manifests -> deploy/ (commit it)
71
+ a2y build # the agent image, pinned end to end
72
+ cp deploy/example.env deploy/.env # secrets; a2y doctor checks parity
73
+ $EDITOR deploy/.env
74
+ a2y provision # prints the messenger account sequence
75
+ a2y up
76
+ a2y auth ana # sign the brains in (once; survives rebuilds)
77
+ a2y doctor # end-to-end checks
78
+ ```
79
+
80
+ Adding a colleague later is one command:
81
+
82
+ ```bash
83
+ a2y agent add cqrtai-pm \
84
+ --description "Project manager for cqrtai: specs, tasks, sequencing." \
85
+ --github-token --projects cqrtai
86
+ ```
87
+
88
+ It validates against the whole fleet (rolling back on failure), re-renders
89
+ `deploy/`, and prints the numbered checklist of what remains (secrets,
90
+ provisioning, sign-in). The command is deliberately non-interactive so that
91
+ **your own assistant agent can run it**: the interview happens in chat, the
92
+ tool call is the answer — see [docs/hiring.md](docs/hiring.md). Running agents
93
+ discover the newcomer without a restart: the fleet roster in every SOUL.md
94
+ regenerates on a loop from the mounted manifests.
95
+
96
+ ## The manifests
97
+
98
+ `fleet.yaml` — deployment-level facts: platform (`mattermost` fully wired;
99
+ other Hermes platforms pass their env through), memory (`hindsight` or `none`),
100
+ network (`bridge` by default; shared VPN namespace as an advanced mode),
101
+ observability (Phoenix traces, Prometheus token metrics), image tag, and
102
+ `defaults` merged into every agent.
103
+
104
+ `agents/<name>/agent.yaml` — the agent: description (its card, roster entry and
105
+ the reason colleagues call it), brain chain and executors, access (ssh volume,
106
+ GitHub token), memory banks, extra MCP servers.
107
+
108
+ `agents/<name>/SOUL.md` — the persona. `SOUL-shared.md` is appended to everyone.
109
+
110
+ `a2y render` turns those into `deploy/`: per-agent Hermes / litellm / acp2api /
111
+ hindsight configs, a compose file, and an `example.env` naming every variable
112
+ the deployment needs. The output is deterministic — same manifests, same bytes —
113
+ so the deploy tree is reviewable and belongs in git. Anything the generator
114
+ does not expose: `agents/<name>/overrides/<file>` replaces the generated file,
115
+ and `hermes:`/`acp2api:` keys in agent.yaml deep-merge into those configs.
116
+
117
+ ## What the pack takes care of
118
+
119
+ - **Session continuity per chat thread** — the `conversation-key` plugin +
120
+ litellm header forwarding + acp2api's conversation keying, so a thread keeps
121
+ one coding-agent session instead of cold-starting per message.
122
+ - **A visible working turn** — progress narration into the chat post while the
123
+ agent works, the trace tucked behind the post's info card when it finishes,
124
+ and mid-turn steering (`/steer`) delivered INTO the running turn.
125
+ - **Fleet discovery without restarts** — a roster generated from every agent's
126
+ own manifest, appended to each SOUL.md on a loop.
127
+ - **Routing without a classifier** — untagged messages claimed by exactly one
128
+ agent from Mattermost facts alone; agent-to-agent messages always need an
129
+ explicit @mention, which is the loop guard.
130
+ - **Memory in tiers** (optional) — a private Hindsight bank per agent written
131
+ automatically, shared project banks written deliberately through tools, and
132
+ bank missions pushed from the repository at every start.
133
+ - **Cost visibility** — acp2api's Prometheus metrics (tokens per agent, per
134
+ executor, per account) and one Phoenix trace project per agent.
135
+ - **The traps already sprung** — device-code-only logins, `CLAUDE_CONFIG_DIR`,
136
+ codex's sandbox mode, cline's self-update, the CA store, the tty-guarded
137
+ bashrc, healthchecks that mean something. They are encoded, not documented.
138
+
139
+ ## Docs
140
+
141
+ - [docs/architecture.md](docs/architecture.md) — the stack and every decision in it
142
+ - [docs/provisioning.md](docs/provisioning.md) — accounts, tokens, sign-ins, keys
143
+ - [docs/hiring.md](docs/hiring.md) — the interview an assistant agent runs to add a colleague
144
+ - [docs/extending.md](docs/extending.md) — custom tools, platforms, derived images
145
+
146
+ ## Development and releasing
147
+
148
+ ```bash
149
+ uv run --group dev pytest # the contract: render determinism, validation, env parity
150
+ uv build # sdist + wheel (the vendored image ships as package data)
151
+ ```
152
+
153
+ Releases publish to PyPI via **trusted publishing** (OIDC — no API token exists
154
+ anywhere): bump `version` in pyproject.toml, commit, `git tag v<version>`,
155
+ `git push --tags`. The workflow gates on the test suite and on the tag matching
156
+ the version. GitHub Actions are pinned by commit SHA, not by tag — a tag can be
157
+ moved onto a poisoned release; a sha cannot.
158
+
159
+ ## Status
160
+
161
+ Extracted from a running deployment; the mattermost + hindsight + claude/codex
162
+ path is the proven one. Telegram/Slack/Discord pass through to Hermes' own
163
+ adapters and are not yet exercised end to end by the maintainers.
@@ -0,0 +1,136 @@
1
+ # agent2you
2
+
3
+ Deploy a **team of chat-resident AI agents** from declarative manifests: each
4
+ agent is one container holding Hermes (the employee), litellm (the failover
5
+ chain), acp2api (an OpenAI endpoint over ACP) and the subscription coding CLIs
6
+ (Claude Code, Codex, opencode, cline) that are its brain and hands.
7
+
8
+ The design was extracted from a production fleet that runs infrastructure,
9
+ product and PM agents as colleagues in Mattermost channels. Every decision in
10
+ this pack — one container per agent, no provider API keys, chat as the only
11
+ inter-agent surface, generated-not-hand-written configs — was measured there
12
+ first.
13
+
14
+ ## What an agent is
15
+
16
+ ```
17
+ chat platform (Mattermost / Telegram / Slack / ...)
18
+ │ @mention
19
+ ┌────┴─────────────────────────────────────────┐
20
+ │ hermes presence, sessions, memory, cron │
21
+ │ litellm failover: next brain on 429 │ one
22
+ │ acp2api OpenAI API ⇄ ACP, per-thread │ container
23
+ │ session continuity │ per agent
24
+ │ claude / codex / opencode / cline │
25
+ │ each spends its own SUBSCRIPTION │
26
+ └───────────────────────────────────────────────┘
27
+ ```
28
+
29
+ No provider API key exists anywhere in a default deployment: the CLIs log in to
30
+ their own subscriptions, which is the legal basis of the arrangement — and the
31
+ reason nothing here can quietly bill an API account.
32
+
33
+ Nothing is shared between agents except memory (optional, bank-per-agent) and
34
+ the chat workspace. Two agents cannot interfere with each other by construction.
35
+
36
+ ## Quickstart
37
+
38
+ ```bash
39
+ uv tool install agent2you # or: pipx install agent2you
40
+
41
+ a2y init myfleet && cd myfleet # a self-contained fleet workspace
42
+ $EDITOR fleet.yaml agents/ana/ # who exists, what platform, what memory
43
+ a2y render # manifests -> deploy/ (commit it)
44
+ a2y build # the agent image, pinned end to end
45
+ cp deploy/example.env deploy/.env # secrets; a2y doctor checks parity
46
+ $EDITOR deploy/.env
47
+ a2y provision # prints the messenger account sequence
48
+ a2y up
49
+ a2y auth ana # sign the brains in (once; survives rebuilds)
50
+ a2y doctor # end-to-end checks
51
+ ```
52
+
53
+ Adding a colleague later is one command:
54
+
55
+ ```bash
56
+ a2y agent add cqrtai-pm \
57
+ --description "Project manager for cqrtai: specs, tasks, sequencing." \
58
+ --github-token --projects cqrtai
59
+ ```
60
+
61
+ It validates against the whole fleet (rolling back on failure), re-renders
62
+ `deploy/`, and prints the numbered checklist of what remains (secrets,
63
+ provisioning, sign-in). The command is deliberately non-interactive so that
64
+ **your own assistant agent can run it**: the interview happens in chat, the
65
+ tool call is the answer — see [docs/hiring.md](docs/hiring.md). Running agents
66
+ discover the newcomer without a restart: the fleet roster in every SOUL.md
67
+ regenerates on a loop from the mounted manifests.
68
+
69
+ ## The manifests
70
+
71
+ `fleet.yaml` — deployment-level facts: platform (`mattermost` fully wired;
72
+ other Hermes platforms pass their env through), memory (`hindsight` or `none`),
73
+ network (`bridge` by default; shared VPN namespace as an advanced mode),
74
+ observability (Phoenix traces, Prometheus token metrics), image tag, and
75
+ `defaults` merged into every agent.
76
+
77
+ `agents/<name>/agent.yaml` — the agent: description (its card, roster entry and
78
+ the reason colleagues call it), brain chain and executors, access (ssh volume,
79
+ GitHub token), memory banks, extra MCP servers.
80
+
81
+ `agents/<name>/SOUL.md` — the persona. `SOUL-shared.md` is appended to everyone.
82
+
83
+ `a2y render` turns those into `deploy/`: per-agent Hermes / litellm / acp2api /
84
+ hindsight configs, a compose file, and an `example.env` naming every variable
85
+ the deployment needs. The output is deterministic — same manifests, same bytes —
86
+ so the deploy tree is reviewable and belongs in git. Anything the generator
87
+ does not expose: `agents/<name>/overrides/<file>` replaces the generated file,
88
+ and `hermes:`/`acp2api:` keys in agent.yaml deep-merge into those configs.
89
+
90
+ ## What the pack takes care of
91
+
92
+ - **Session continuity per chat thread** — the `conversation-key` plugin +
93
+ litellm header forwarding + acp2api's conversation keying, so a thread keeps
94
+ one coding-agent session instead of cold-starting per message.
95
+ - **A visible working turn** — progress narration into the chat post while the
96
+ agent works, the trace tucked behind the post's info card when it finishes,
97
+ and mid-turn steering (`/steer`) delivered INTO the running turn.
98
+ - **Fleet discovery without restarts** — a roster generated from every agent's
99
+ own manifest, appended to each SOUL.md on a loop.
100
+ - **Routing without a classifier** — untagged messages claimed by exactly one
101
+ agent from Mattermost facts alone; agent-to-agent messages always need an
102
+ explicit @mention, which is the loop guard.
103
+ - **Memory in tiers** (optional) — a private Hindsight bank per agent written
104
+ automatically, shared project banks written deliberately through tools, and
105
+ bank missions pushed from the repository at every start.
106
+ - **Cost visibility** — acp2api's Prometheus metrics (tokens per agent, per
107
+ executor, per account) and one Phoenix trace project per agent.
108
+ - **The traps already sprung** — device-code-only logins, `CLAUDE_CONFIG_DIR`,
109
+ codex's sandbox mode, cline's self-update, the CA store, the tty-guarded
110
+ bashrc, healthchecks that mean something. They are encoded, not documented.
111
+
112
+ ## Docs
113
+
114
+ - [docs/architecture.md](docs/architecture.md) — the stack and every decision in it
115
+ - [docs/provisioning.md](docs/provisioning.md) — accounts, tokens, sign-ins, keys
116
+ - [docs/hiring.md](docs/hiring.md) — the interview an assistant agent runs to add a colleague
117
+ - [docs/extending.md](docs/extending.md) — custom tools, platforms, derived images
118
+
119
+ ## Development and releasing
120
+
121
+ ```bash
122
+ uv run --group dev pytest # the contract: render determinism, validation, env parity
123
+ uv build # sdist + wheel (the vendored image ships as package data)
124
+ ```
125
+
126
+ Releases publish to PyPI via **trusted publishing** (OIDC — no API token exists
127
+ anywhere): bump `version` in pyproject.toml, commit, `git tag v<version>`,
128
+ `git push --tags`. The workflow gates on the test suite and on the tag matching
129
+ the version. GitHub Actions are pinned by commit SHA, not by tag — a tag can be
130
+ moved onto a poisoned release; a sha cannot.
131
+
132
+ ## Status
133
+
134
+ Extracted from a running deployment; the mattermost + hindsight + claude/codex
135
+ path is the proven one. Telegram/Slack/Discord pass through to Hermes' own
136
+ adapters and are not yet exercised end to end by the maintainers.
@@ -0,0 +1,3 @@
1
+ """agent2you -- deploy a team of chat-resident AI agents from declarative manifests."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,184 @@
1
+ """`a2y agent ...` -- manage agents in a fleet workspace.
2
+
3
+ `a2y agent add` is deliberately non-interactive and single-shot: the intended
4
+ interactive layer is a fleet agent (the assistant) interviewing the operator in
5
+ chat and then calling this command with the answers. The tool stays
6
+ deterministic; the conversation stays where conversations belong. See
7
+ docs/hiring.md for the interview the assistant runs.
8
+
9
+ Structured input: `--json` accepts a full agent.yaml body (file path or `-` for
10
+ stdin), for callers that would rather build the manifest than spell flags.
11
+ Flags win over `--json` keys.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import json
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ import yaml
22
+
23
+ from .manifest import NAME_RE, ManifestError, load_fleet
24
+
25
+ SOUL_SKELETON = """\
26
+ # {name}
27
+
28
+ You are {name}. {description}
29
+
30
+ ## Scope
31
+
32
+ - What you own and answer for. Be precise: your colleagues route by this.
33
+ - What you do NOT touch, even when convenient.
34
+
35
+ ## How you work
36
+
37
+ - Never report success for a tool call that failed; quote the error instead.
38
+ - "Posted" is not "delivered": a mention reaches only members of that channel.
39
+ """
40
+
41
+
42
+ def cmd_agent_add(ns: argparse.Namespace) -> int:
43
+ root = Path.cwd()
44
+ if not (root / "fleet.yaml").is_file():
45
+ print("a2y: no fleet.yaml here -- run from a fleet workspace", file=sys.stderr)
46
+ return 2
47
+
48
+ name = ns.name
49
+ if not NAME_RE.match(name):
50
+ print(f"a2y: agent name {name!r} must be lowercase [a-z0-9-]", file=sys.stderr)
51
+ return 2
52
+ agent_dir = root / "agents" / name
53
+ if agent_dir.exists():
54
+ print(f"a2y: agents/{name}/ already exists", file=sys.stderr)
55
+ return 2
56
+
57
+ manifest: dict = {}
58
+ if ns.json:
59
+ raw = sys.stdin.read() if ns.json == "-" else Path(ns.json).read_text()
60
+ try:
61
+ manifest = json.loads(raw)
62
+ except json.JSONDecodeError as exc:
63
+ print(f"a2y: --json is not valid JSON ({exc})", file=sys.stderr)
64
+ return 2
65
+ if not isinstance(manifest, dict):
66
+ print("a2y: --json must be an object (the agent.yaml body)", file=sys.stderr)
67
+ return 2
68
+
69
+ manifest["name"] = name
70
+ if ns.description:
71
+ manifest["description"] = ns.description
72
+ if not str(manifest.get("description") or "").strip():
73
+ print("a2y: --description is required (it is the agent's card and roster entry)",
74
+ file=sys.stderr)
75
+ return 2
76
+
77
+ if ns.chain:
78
+ brains = manifest.setdefault("brains", {})
79
+ brains["chain"] = [s.strip() for s in ns.chain.split(",") if s.strip()]
80
+ if ns.ssh or ns.github_token:
81
+ access = manifest.setdefault("access", {})
82
+ if ns.ssh:
83
+ access["ssh"] = True
84
+ if ns.github_token:
85
+ access["github_token"] = True
86
+ if ns.projects:
87
+ memory = manifest.setdefault("memory", {})
88
+ memory["projects"] = [s.strip() for s in ns.projects.split(",") if s.strip()]
89
+ if ns.reply_mode or ns.no_require_mention:
90
+ platform = manifest.setdefault("platform", {})
91
+ if ns.reply_mode:
92
+ platform["reply_mode"] = ns.reply_mode
93
+ if ns.no_require_mention:
94
+ platform["require_mention"] = False
95
+ if ns.ports_base:
96
+ manifest["ports"] = {"base": int(ns.ports_base)}
97
+
98
+ soul = SOUL_SKELETON.format(name=name, description=manifest["description"])
99
+ if ns.soul_file:
100
+ soul = sys.stdin.read() if ns.soul_file == "-" else Path(ns.soul_file).read_text()
101
+
102
+ # Write, then validate by loading the whole fleet; roll back on failure so a
103
+ # bad call leaves no half-created agent behind.
104
+ agent_dir.mkdir(parents=True)
105
+ (agent_dir / "agent.yaml").write_text(
106
+ yaml.safe_dump(manifest, sort_keys=False, allow_unicode=True, width=100))
107
+ (agent_dir / "SOUL.md").write_text(soul)
108
+ try:
109
+ fleet = load_fleet(root)
110
+ except ManifestError as exc:
111
+ (agent_dir / "agent.yaml").unlink()
112
+ (agent_dir / "SOUL.md").unlink()
113
+ agent_dir.rmdir()
114
+ print(f"a2y: rolled back agents/{name}/ -- {exc}", file=sys.stderr)
115
+ return 2
116
+
117
+ print(f" created agents/{name}/agent.yaml")
118
+ print(f" created agents/{name}/SOUL.md" + ("" if ns.soul_file else " (skeleton -- write the real soul)"))
119
+
120
+ if not ns.no_render:
121
+ from .render import render_fleet
122
+ for rel in render_fleet(fleet):
123
+ print(f" wrote deploy/{rel}")
124
+
125
+ agent = next(a for a in fleet.agents if a.name == name)
126
+ p = agent.env_prefix
127
+ print(f"\n=== next steps for {name} ===")
128
+ step = 1
129
+ print(f" {step}. add to deploy/.env: {p}_LITELLM_MASTER_KEY=<random>"); step += 1
130
+ if fleet.platform_kind == "mattermost":
131
+ print(f" {step}. `a2y provision {name}` -- create the Mattermost account, then set")
132
+ print(f" {p}_MATTERMOST_TOKEN, {p}_MATTERMOST_HOME_CHANNEL (and empty {p}_MATTERMOST_CHANNELS)")
133
+ step += 1
134
+ print(f" {step}. append the new USER ID to A2Y_MATTERMOST_ALLOWED_USERS and RECREATE the")
135
+ print(f" other agents (`a2y up` recreates on env change) -- without this, messages")
136
+ print(f" from {name} are dropped silently"); step += 1
137
+ if agent.access.get("github_token"):
138
+ print(f" {step}. set {p}_GH_TOKEN (fine-grained PAT scoped to its repositories)"); step += 1
139
+ print(f" {step}. `a2y up {name}`"); step += 1
140
+ print(f" {step}. `a2y auth {name}` -- sign the brains in (device-code flows)"); step += 1
141
+ if agent.access.get("ssh"):
142
+ print(f" {step}. register the git deploy key the entrypoint prints on first start"); step += 1
143
+ print(f" {step}. verify with a real mention in the channel; `a2y doctor` last")
144
+ return 0
145
+
146
+
147
+ def cmd_agent_list(_: argparse.Namespace) -> int:
148
+ fleet = load_fleet(Path.cwd())
149
+ for a in fleet.agents:
150
+ marks = []
151
+ if a.access.get("ssh"):
152
+ marks.append("ssh")
153
+ if a.access.get("github_token"):
154
+ marks.append("gh-token")
155
+ if a.project_banks():
156
+ marks.append("projects:" + ",".join(a.project_banks()))
157
+ suffix = f" [{'; '.join(marks)}]" if marks else ""
158
+ print(f" {a.name} ({' -> '.join(a.chain)}){suffix}")
159
+ print(f" {a.description}")
160
+ return 0
161
+
162
+
163
+ def register(sub: argparse._SubParsersAction) -> None:
164
+ p = sub.add_parser("agent", help="manage agents (add, list)")
165
+ ssub = p.add_subparsers(dest="agent_cmd", required=True)
166
+
167
+ pa = ssub.add_parser(
168
+ "add", help="add an agent non-interactively (built for being called BY an agent)")
169
+ pa.add_argument("name")
170
+ pa.add_argument("--description", help="what the agent owns and answers for (required)")
171
+ pa.add_argument("--chain", help="brain chain, e.g. claude,codex (default: fleet defaults)")
172
+ pa.add_argument("--ssh", action="store_true", help="mount an ssh key volume")
173
+ pa.add_argument("--github-token", action="store_true", help="expects AGENT_<N>_GH_TOKEN")
174
+ pa.add_argument("--projects", help="shared memory banks, comma-separated")
175
+ pa.add_argument("--reply-mode", choices=["thread", "channel"])
176
+ pa.add_argument("--no-require-mention", action="store_true")
177
+ pa.add_argument("--ports-base", help="port block base (shared-namespace fleets only)")
178
+ pa.add_argument("--soul-file", help="SOUL.md content from a file, or - for stdin")
179
+ pa.add_argument("--json", help="full agent.yaml body as JSON (file or -); flags win")
180
+ pa.add_argument("--no-render", action="store_true")
181
+ pa.set_defaults(fn=cmd_agent_add)
182
+
183
+ pl = ssub.add_parser("list", help="list agents with chain and access")
184
+ pl.set_defaults(fn=cmd_agent_list)