Skip to content

Commit f9fe746

Browse files
Install contact task runners for every Fleet agent (#225)
Co-authored-by: Anthony Ettinger <anthony@profullstack.com>
1 parent 490de0d commit f9fe746

5 files changed

Lines changed: 181 additions & 119 deletions

File tree

‎dev2/agents/README.md‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
# RiotCoder contact task runner
1+
# Fleet agent contact task runners
22

3-
`qc-agent-responder.py` handles QryptChat for the Linux user who owns the QryptChat session. It
4-
keeps ordinary replies in chat mode and queues authenticated Anthony instructions for
5-
`agent-llm riotcoder full`. The worker runs with RiotCoder's UID, writes private task state under
6-
his home, and sends its result to the originating QryptChat conversation.
3+
Each agent's QryptChat responder runs with that agent's Linux UID. Normal messages stay in chat
4+
mode; authenticated instructions from Anthony enter that user's `agent-llm <agent> full` runner.
5+
Task state stays in the agent's home and results return to the originating QryptChat conversation.
76

8-
The group.call phone worker sends trusted caller requests to the same queue. `agent-mail-poll`
9-
polls only RiotCoder's mailbox; before work, the agent checks the Forward Email DMARC verdict and
10-
claims the message, then replies from RiotCoder's own SMTP settings. Do not place RiotCoder's mail
11-
configuration or task state in Anthony's home.
7+
Each agent has a separately scoped group.call worker key, private phone settings, and an email
8+
poller running under that agent's UID. Email actions require the trusted Anthony DMARC verdict;
9+
the shared claim directory prevents forwarded copies from triggering duplicate work. Mailbox
10+
credentials, QryptChat sessions, model logins, and task state stay in their owner's home.
1211

13-
On dev2, deploy the CLI package and its production dependencies to `/opt/riotcoder-cli`, provision
14-
`/home/riotcoder/.config/cli-tools/mail.json` with only the RiotCoder account, then run
15-
`bash dev2/agents/install-riotcoder.sh` as root. The installer grants RiotCoder's group read
16-
access to the deployed CLI and places the responder and mail tools in the correct homes.
17-
The mail poller runs as UID RiotCoder.
12+
On dev2, deploy the CLI package and its production dependencies to `/opt/riotcoder-cli`, give each
13+
ready agent a private `agent-llm.json`, QryptChat session, model login, and one-account
14+
`mail.json`, then run `bash dev2/agents/install-agent-runners.sh` as root. The installer grants
15+
the non-admin Fleet agent group read access to the CLI and installs a mail timer for every ready
16+
account. An agent missing its own credentials is reported and skipped; the installer never borrows
17+
Anthony's or another agent's login.

‎dev2/agents/agent-mail-claim‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
#!/bin/sh
2-
# agent-mail-claim <agent> <message-id>: one email, one agent. Anthony often sends a forward to
3-
# bot@, cha0spunk@ and riotcoder@ at once, so both mailboxes hold it. The first agent to claim the
4-
# Message-ID owns it (mkdir is atomic); the other sees who did and leaves it.
2+
# agent-mail-claim <agent> <message-id>: one email, one agent. When mail is
3+
# delivered to more than one agent, the shared private claim directory makes
4+
# the first claim atomic; the other agent sees who owns it and leaves it.
55
# exit 0 + "claimed" this agent owns it now (or already did)
66
# exit 1 + "taken by <agent>" the other agent owns it
77
set -e
8-
umask 077
8+
umask 0007
99
agent="$1"; id="$2"
1010
[ -n "$agent" ] && [ -n "$id" ] || { echo "usage: agent-mail-claim <agent> <message-id>" >&2; exit 2; }
11-
dir="$HOME/.local/share/agent-mail/claims"
12-
mkdir -p "$dir"
13-
chmod 0700 "$dir"
11+
if [ -n "${AGENT_MAIL_CLAIM_ROOT:-}" ]; then
12+
dir="$AGENT_MAIL_CLAIM_ROOT"
13+
[ -d "$dir" ] && [ -w "$dir" ] || { echo "shared mail claim directory is unavailable" >&2; exit 1; }
14+
else
15+
dir="$HOME/.local/share/agent-mail/claims"
16+
mkdir -p "$dir"
17+
chmod 0700 "$dir"
18+
fi
1419
key=$(printf '%s' "$id" | sha256sum | cut -c1-32)
1520
if mkdir "$dir/$key" 2>/dev/null; then
16-
printf '%s\n%s\n%s\n' "$agent" "$id" "$(date -u +%FT%TZ)" > "$dir/$key/owner"
21+
# The shared directory contains only the handle that won the atomic claim.
22+
# Keep message IDs private in each agent's own queue and logs.
23+
printf '%s\n' "$agent" > "$dir/$key/owner"
24+
chmod 2770 "$dir/$key"
25+
chmod 0640 "$dir/$key/owner"
1726
echo claimed; exit 0
1827
fi
1928
owner=$(head -1 "$dir/$key/owner" 2>/dev/null || echo unknown)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
if [ "$(id -u)" -ne 0 ]; then
5+
echo "run this installer as root" >&2
6+
exit 1
7+
fi
8+
9+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
AGENTS=(cha0spunk coinpay knox nora quill riotcoder rootkey)
11+
MAIL_GROUP=fleet-agent-mail
12+
CLI_DIR=/opt/riotcoder-cli
13+
NODE_DIR=/home/riotcoder/.local/share/agent-phone
14+
CLAIM_DIR=/var/lib/fleet-agent-mail/claims
15+
16+
id riotcoder >/dev/null
17+
[ -x "$NODE_DIR/node" ] || { echo "deploy group.call first to install private Node runtimes" >&2; exit 1; }
18+
[ -d "$CLI_DIR/node_modules" ] || { echo "CLI runtime dependencies are missing at $CLI_DIR" >&2; exit 1; }
19+
getent group "$MAIL_GROUP" >/dev/null || groupadd --system "$MAIL_GROUP"
20+
for agent in "${AGENTS[@]}"; do
21+
id "$agent" >/dev/null || { echo "missing Linux account: $agent" >&2; exit 1; }
22+
groups=" $(id -nG "$agent") "
23+
case "$groups" in *" sudo "*|*" wheel "*|*" admin "*) echo "$agent has an admin group" >&2; exit 1;; esac
24+
usermod -a -G "$MAIL_GROUP" "$agent"
25+
done
26+
27+
# Root owns the deployed code. Only Fleet agent accounts need to read it.
28+
chgrp -R "$MAIL_GROUP" "$CLI_DIR"
29+
chmod -R o-rwx "$CLI_DIR"
30+
chmod -R g+rX "$CLI_DIR"
31+
install -d -o root -g "$MAIL_GROUP" -m 2770 /var/lib/fleet-agent-mail "$CLAIM_DIR"
32+
install -o root -g root -m 0755 "$SOURCE_DIR/qc-agent-responder.py" /usr/local/libexec/qc-agent-responder
33+
34+
ready=()
35+
skipped=()
36+
for agent in "${AGENTS[@]}"; do
37+
home="$(getent passwd "$agent" | cut -d: -f6)"
38+
group="$(id -gn "$agent")"
39+
node="$home/.local/share/agent-phone/node"
40+
install -d -o "$agent" -g "$group" -m 0750 "$home/.config/agent-identities/$agent"
41+
identity_source="/home/anthony/.config/agent-identities/$agent"
42+
if [ -f "$identity_source/mail.env" ]; then
43+
install -o "$agent" -g "$group" -m 0600 "$identity_source/mail.env" "$home/.config/agent-identities/$agent/mail.env"
44+
rm -f "$identity_source/mail.env"
45+
fi
46+
if [ -f "$identity_source/mail-prompt.md" ]; then
47+
install -o "$agent" -g "$group" -m 0640 "$identity_source/mail-prompt.md" "$home/.config/agent-identities/$agent/mail-prompt.md"
48+
fi
49+
if [ -f "$identity_source/AGENTS.md" ]; then
50+
install -o "$agent" -g "$group" -m 0640 "$identity_source/AGENTS.md" "$home/.config/agent-identities/$agent/AGENTS.md"
51+
fi
52+
if [ -f "$identity_source/profile.json" ]; then
53+
install -o "$agent" -g "$group" -m 0640 "$identity_source/profile.json" "$home/.config/agent-identities/$agent/profile.json"
54+
fi
55+
missing=()
56+
for path in \
57+
"$home/.config/agent-llm.json" \
58+
"$home/.config/cli-tools/mail.json" \
59+
"$home/.codex/auth.json" \
60+
"$home/.config/qc/session.json" \
61+
"$home/.config/qc-agent/qc-passphrase" \
62+
"$home/.config/qc-agent/persona.md"; do
63+
[ -f "$path" ] || missing+=("${path#"$home"/}")
64+
done
65+
if [ "${#missing[@]}" -gt 0 ]; then
66+
skipped+=("$agent (${missing[*]})")
67+
continue
68+
fi
69+
if [ ! -x "$node" ]; then
70+
skipped+=("$agent (missing private Node runtime)")
71+
continue
72+
fi
73+
74+
install -d -o "$agent" -g "$group" -m 0750 "$home/.local/bin" "$home/.local/state/agent-mail-poll"
75+
install -o "$agent" -g "$group" -m 0750 "$SOURCE_DIR/agent-mail-trusted.py" "$home/.local/bin/agent-mail-trusted"
76+
install -o "$agent" -g "$group" -m 0750 "$SOURCE_DIR/agent-mail-claim" "$home/.local/bin/agent-mail-claim"
77+
78+
cat > "$home/.local/bin/mail.tmp" <<EOF
79+
#!/bin/sh
80+
export HOME=$home
81+
exec $node $CLI_DIR/bin/mail.ts "\$@"
82+
EOF
83+
chown "$agent:$group" "$home/.local/bin/mail.tmp"
84+
chmod 0750 "$home/.local/bin/mail.tmp"
85+
mv "$home/.local/bin/mail.tmp" "$home/.local/bin/mail"
86+
87+
cat > "$home/.local/bin/agent-mail-poll.tmp" <<EOF
88+
#!/bin/sh
89+
export HOME=$home
90+
exec $node $CLI_DIR/bin/agent-mail-poll.ts "\$@"
91+
EOF
92+
chown "$agent:$group" "$home/.local/bin/agent-mail-poll.tmp"
93+
chmod 0750 "$home/.local/bin/agent-mail-poll.tmp"
94+
mv "$home/.local/bin/agent-mail-poll.tmp" "$home/.local/bin/agent-mail-poll"
95+
96+
cat > "/etc/systemd/system/agent-mail-poll-$agent.service" <<EOF
97+
[Unit]
98+
Description=$agent trusted email task poller
99+
After=network-online.target
100+
Wants=network-online.target
101+
102+
[Service]
103+
Type=oneshot
104+
User=$agent
105+
Group=$group
106+
SupplementaryGroups=$MAIL_GROUP
107+
WorkingDirectory=$home
108+
Environment=HOME=$home
109+
Environment=AGENT_MAIL_CLAIM_ROOT=$CLAIM_DIR
110+
Environment=PATH=$home/.local/share/agent-phone:$home/.local/bin:/usr/local/bin:/usr/bin:/bin
111+
ExecStart=$home/.local/bin/agent-mail-poll poll --agent $agent
112+
EOF
113+
114+
cat > "/etc/systemd/system/agent-mail-poll-$agent.timer" <<EOF
115+
[Unit]
116+
Description=Poll $agent's inbox every five minutes
117+
118+
[Timer]
119+
OnCalendar=*-*-* *:0/5:00
120+
AccuracySec=30s
121+
Persistent=true
122+
123+
[Install]
124+
WantedBy=timers.target
125+
EOF
126+
chmod 0644 "/etc/systemd/system/agent-mail-poll-$agent.service" "/etc/systemd/system/agent-mail-poll-$agent.timer"
127+
ready+=("$agent")
128+
done
129+
130+
systemctl daemon-reload
131+
for agent in "${ready[@]}"; do
132+
systemctl enable --now "agent-mail-poll-$agent.timer" >/dev/null
133+
done
134+
printf 'Installed contact runners for: %s\n' "${ready[*]:-(none)}"
135+
if [ "${#skipped[@]}" -gt 0 ]; then
136+
printf 'Skipped accounts missing their own credentials: %s\n' "${skipped[*]}"
137+
fi

‎dev2/agents/install-riotcoder.sh‎

Lines changed: 1 addition & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,4 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
3-
4-
if [ "$(id -u)" -ne 0 ]; then
5-
echo "run this installer as root" >&2
6-
exit 1
7-
fi
8-
93
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10-
REPO_DIR="$(cd "$SOURCE_DIR/../.." && pwd)"
11-
USER_NAME=riotcoder
12-
USER_HOME=/home/riotcoder
13-
GROUP_NAME=riotcoder
14-
NODE="$USER_HOME/.local/share/agent-phone/node"
15-
CLI_DIR=/opt/riotcoder-cli
16-
17-
id "$USER_NAME" >/dev/null
18-
[ -x "$NODE" ] || { echo "RiotCoder's private Node runtime is missing; deploy group.call first" >&2; exit 1; }
19-
[ -d "$CLI_DIR/node_modules" ] || { echo "CLI runtime dependencies are missing at $CLI_DIR" >&2; exit 1; }
20-
[ -f "$USER_HOME/.config/cli-tools/mail.json" ] || { echo "RiotCoder's private mail configuration is missing" >&2; exit 1; }
21-
22-
# The deploy checkout and dependencies are installed by root; grant RiotCoder's
23-
# own group read/traverse access so the account can execute its CLI tools.
24-
chgrp -R "$GROUP_NAME" "$CLI_DIR"
25-
chmod -R g+rX "$CLI_DIR"
26-
27-
install -d -o "$USER_NAME" -g "$GROUP_NAME" -m 0750 \
28-
"$USER_HOME/.local/bin" "$USER_HOME/.config/agent-identities/riotcoder" "$USER_HOME/.local/state/agent-mail-poll"
29-
install -o root -g root -m 0755 "$SOURCE_DIR/qc-agent-responder.py" /usr/local/libexec/qc-agent-responder
30-
install -o "$USER_NAME" -g "$GROUP_NAME" -m 0755 "$SOURCE_DIR/agent-mail-trusted.py" "$USER_HOME/.local/bin/agent-mail-trusted"
31-
install -o "$USER_NAME" -g "$GROUP_NAME" -m 0755 "$SOURCE_DIR/agent-mail-claim" "$USER_HOME/.local/bin/agent-mail-claim"
32-
install -o "$USER_NAME" -g "$GROUP_NAME" -m 0644 "$SOURCE_DIR/riotcoder-mail-prompt.md" "$USER_HOME/.config/agent-identities/riotcoder/mail-prompt.md"
33-
34-
cat > "$USER_HOME/.local/bin/mail.tmp" <<EOF
35-
#!/bin/sh
36-
export HOME=$USER_HOME
37-
exec $NODE $CLI_DIR/bin/mail.ts "\$@"
38-
EOF
39-
chown "$USER_NAME:$GROUP_NAME" "$USER_HOME/.local/bin/mail.tmp"
40-
chmod 0750 "$USER_HOME/.local/bin/mail.tmp"
41-
mv "$USER_HOME/.local/bin/mail.tmp" "$USER_HOME/.local/bin/mail"
42-
43-
cat > "$USER_HOME/.local/bin/agent-mail-poll.tmp" <<EOF
44-
#!/bin/sh
45-
export HOME=$USER_HOME
46-
exec $NODE $CLI_DIR/bin/agent-mail-poll.ts "\$@"
47-
EOF
48-
chown "$USER_NAME:$GROUP_NAME" "$USER_HOME/.local/bin/agent-mail-poll.tmp"
49-
chmod 0750 "$USER_HOME/.local/bin/agent-mail-poll.tmp"
50-
mv "$USER_HOME/.local/bin/agent-mail-poll.tmp" "$USER_HOME/.local/bin/agent-mail-poll"
51-
52-
cat > /etc/systemd/system/agent-mail-poll-riotcoder.service <<EOF
53-
[Unit]
54-
Description=RiotCoder trusted email task poller
55-
After=network-online.target
56-
Wants=network-online.target
57-
58-
[Service]
59-
Type=oneshot
60-
User=riotcoder
61-
Group=riotcoder
62-
WorkingDirectory=/home/riotcoder
63-
Environment=HOME=/home/riotcoder
64-
Environment=PATH=/home/riotcoder/.local/share/agent-phone:/home/riotcoder/.local/bin:/usr/local/bin:/usr/bin:/bin
65-
ExecStart=/home/riotcoder/.local/bin/agent-mail-poll poll --agent riotcoder
66-
EOF
67-
68-
cat > /etc/systemd/system/agent-mail-poll-riotcoder.timer <<'EOF'
69-
[Unit]
70-
Description=Poll RiotCoder's inbox every five minutes
71-
72-
[Timer]
73-
OnCalendar=*-*-* *:0/5:00
74-
AccuracySec=30s
75-
Persistent=true
76-
77-
[Install]
78-
WantedBy=timers.target
79-
EOF
80-
81-
chmod 0644 /etc/systemd/system/agent-mail-poll-riotcoder.{service,timer}
82-
systemctl daemon-reload
83-
systemctl enable --now agent-mail-poll-riotcoder.timer >/dev/null
84-
echo "RiotCoder QryptChat and mail task runners installed under UID $(id -u "$USER_NAME")."
4+
exec "$SOURCE_DIR/install-agent-runners.sh" "$@"

0 commit comments

Comments
 (0)