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.
- agent2you-1.0.0/LICENSE +21 -0
- agent2you-1.0.0/PKG-INFO +163 -0
- agent2you-1.0.0/README.md +136 -0
- agent2you-1.0.0/a2y/__init__.py +3 -0
- agent2you-1.0.0/a2y/agents_cmd.py +184 -0
- agent2you-1.0.0/a2y/cli.py +264 -0
- agent2you-1.0.0/a2y/doctor.py +116 -0
- agent2you-1.0.0/a2y/image/agent.dockerfile +215 -0
- agent2you-1.0.0/a2y/image/apply-memory-profile.py +123 -0
- agent2you-1.0.0/a2y/image/bashrc +39 -0
- agent2you-1.0.0/a2y/image/entrypoint.sh +282 -0
- agent2you-1.0.0/a2y/image/fleet-roster.py +124 -0
- agent2you-1.0.0/a2y/image/plugins/_.hint +63 -0
- agent2you-1.0.0/a2y/image/plugins/conversation-key/__init__.py +61 -0
- agent2you-1.0.0/a2y/image/plugins/conversation-key/plugin.yaml +8 -0
- agent2you-1.0.0/a2y/image/plugins/hermes_otel/config.yaml +62 -0
- agent2you-1.0.0/a2y/image/plugins/mention-on-edit/__init__.py +301 -0
- agent2you-1.0.0/a2y/image/plugins/mention-on-edit/plugin.yaml +7 -0
- agent2you-1.0.0/a2y/image/plugins/reasoning-live/__init__.py +435 -0
- agent2you-1.0.0/a2y/image/plugins/reasoning-live/plugin.yaml +9 -0
- agent2you-1.0.0/a2y/image/plugins/steer-into-turn/__init__.py +419 -0
- agent2you-1.0.0/a2y/image/plugins/steer-into-turn/plugin.yaml +9 -0
- agent2you-1.0.0/a2y/image/plugins/trace-to-card/__init__.py +329 -0
- agent2you-1.0.0/a2y/image/plugins/trace-to-card/plugin.yaml +12 -0
- agent2you-1.0.0/a2y/image/plugins/untagged-routing/__init__.py +329 -0
- agent2you-1.0.0/a2y/image/plugins/untagged-routing/plugin.yaml +12 -0
- agent2you-1.0.0/a2y/image/plugins/untagged-routing/policy.py +160 -0
- agent2you-1.0.0/a2y/image/supervisord.conf +83 -0
- agent2you-1.0.0/a2y/manifest.py +278 -0
- agent2you-1.0.0/a2y/render.py +633 -0
- agent2you-1.0.0/a2y/scaffold.py +159 -0
- agent2you-1.0.0/agent2you.egg-info/PKG-INFO +163 -0
- agent2you-1.0.0/agent2you.egg-info/SOURCES.txt +39 -0
- agent2you-1.0.0/agent2you.egg-info/dependency_links.txt +1 -0
- agent2you-1.0.0/agent2you.egg-info/entry_points.txt +2 -0
- agent2you-1.0.0/agent2you.egg-info/requires.txt +1 -0
- agent2you-1.0.0/agent2you.egg-info/top_level.txt +1 -0
- agent2you-1.0.0/pyproject.toml +47 -0
- agent2you-1.0.0/setup.cfg +4 -0
- agent2you-1.0.0/tests/test_agent_add.py +60 -0
- agent2you-1.0.0/tests/test_render.py +130 -0
agent2you-1.0.0/LICENSE
ADDED
|
@@ -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.
|
agent2you-1.0.0/PKG-INFO
ADDED
|
@@ -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,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)
|