Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions dev2/agents/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# RiotCoder contact task runner
# Fleet agent contact task runners

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

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

On dev2, deploy the CLI package and its production dependencies to `/opt/riotcoder-cli`, provision
`/home/riotcoder/.config/cli-tools/mail.json` with only the RiotCoder account, then run
`bash dev2/agents/install-riotcoder.sh` as root. The installer grants RiotCoder's group read
access to the deployed CLI and places the responder and mail tools in the correct homes.
The mail poller runs as UID RiotCoder.
On dev2, deploy the CLI package and its production dependencies to `/opt/riotcoder-cli`, give each
ready agent a private `agent-llm.json`, QryptChat session, model login, and one-account
`mail.json`, then run `bash dev2/agents/install-agent-runners.sh` as root. The installer grants
the non-admin Fleet agent group read access to the CLI and installs a mail timer for every ready
account. An agent missing its own credentials is reported and skipped; the installer never borrows
Anthony's or another agent's login.
25 changes: 17 additions & 8 deletions dev2/agents/agent-mail-claim
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
#!/bin/sh
# agent-mail-claim <agent> <message-id>: one email, one agent. Anthony often sends a forward to
# bot@, cha0spunk@ and riotcoder@ at once, so both mailboxes hold it. The first agent to claim the
# Message-ID owns it (mkdir is atomic); the other sees who did and leaves it.
# agent-mail-claim <agent> <message-id>: one email, one agent. When mail is
# delivered to more than one agent, the shared private claim directory makes
# the first claim atomic; the other agent sees who owns it and leaves it.
# exit 0 + "claimed" this agent owns it now (or already did)
# exit 1 + "taken by <agent>" the other agent owns it
set -e
umask 077
umask 0007
agent="$1"; id="$2"
[ -n "$agent" ] && [ -n "$id" ] || { echo "usage: agent-mail-claim <agent> <message-id>" >&2; exit 2; }
dir="$HOME/.local/share/agent-mail/claims"
mkdir -p "$dir"
chmod 0700 "$dir"
if [ -n "${AGENT_MAIL_CLAIM_ROOT:-}" ]; then
dir="$AGENT_MAIL_CLAIM_ROOT"
[ -d "$dir" ] && [ -w "$dir" ] || { echo "shared mail claim directory is unavailable" >&2; exit 1; }
else
dir="$HOME/.local/share/agent-mail/claims"
mkdir -p "$dir"
chmod 0700 "$dir"
fi
key=$(printf '%s' "$id" | sha256sum | cut -c1-32)
if mkdir "$dir/$key" 2>/dev/null; then
printf '%s\n%s\n%s\n' "$agent" "$id" "$(date -u +%FT%TZ)" > "$dir/$key/owner"
# The shared directory contains only the handle that won the atomic claim.
# Keep message IDs private in each agent's own queue and logs.
printf '%s\n' "$agent" > "$dir/$key/owner"
chmod 2770 "$dir/$key"
chmod 0640 "$dir/$key/owner"
echo claimed; exit 0
fi
owner=$(head -1 "$dir/$key/owner" 2>/dev/null || echo unknown)
Expand Down
137 changes: 137 additions & 0 deletions dev2/agents/install-agent-runners.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -ne 0 ]; then
echo "run this installer as root" >&2
exit 1
fi

SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENTS=(cha0spunk coinpay knox nora quill riotcoder rootkey)
MAIL_GROUP=fleet-agent-mail
CLI_DIR=/opt/riotcoder-cli
NODE_DIR=/home/riotcoder/.local/share/agent-phone
CLAIM_DIR=/var/lib/fleet-agent-mail/claims

id riotcoder >/dev/null
[ -x "$NODE_DIR/node" ] || { echo "deploy group.call first to install private Node runtimes" >&2; exit 1; }
[ -d "$CLI_DIR/node_modules" ] || { echo "CLI runtime dependencies are missing at $CLI_DIR" >&2; exit 1; }
getent group "$MAIL_GROUP" >/dev/null || groupadd --system "$MAIL_GROUP"
for agent in "${AGENTS[@]}"; do
id "$agent" >/dev/null || { echo "missing Linux account: $agent" >&2; exit 1; }
groups=" $(id -nG "$agent") "
case "$groups" in *" sudo "*|*" wheel "*|*" admin "*) echo "$agent has an admin group" >&2; exit 1;; esac
usermod -a -G "$MAIL_GROUP" "$agent"
done

# Root owns the deployed code. Only Fleet agent accounts need to read it.
chgrp -R "$MAIL_GROUP" "$CLI_DIR"
chmod -R o-rwx "$CLI_DIR"
chmod -R g+rX "$CLI_DIR"
install -d -o root -g "$MAIL_GROUP" -m 2770 /var/lib/fleet-agent-mail "$CLAIM_DIR"
install -o root -g root -m 0755 "$SOURCE_DIR/qc-agent-responder.py" /usr/local/libexec/qc-agent-responder

ready=()
skipped=()
for agent in "${AGENTS[@]}"; do
home="$(getent passwd "$agent" | cut -d: -f6)"
group="$(id -gn "$agent")"
node="$home/.local/share/agent-phone/node"
install -d -o "$agent" -g "$group" -m 0750 "$home/.config/agent-identities/$agent"
identity_source="/home/anthony/.config/agent-identities/$agent"
if [ -f "$identity_source/mail.env" ]; then
install -o "$agent" -g "$group" -m 0600 "$identity_source/mail.env" "$home/.config/agent-identities/$agent/mail.env"
rm -f "$identity_source/mail.env"
fi
if [ -f "$identity_source/mail-prompt.md" ]; then
install -o "$agent" -g "$group" -m 0640 "$identity_source/mail-prompt.md" "$home/.config/agent-identities/$agent/mail-prompt.md"
fi
if [ -f "$identity_source/AGENTS.md" ]; then
install -o "$agent" -g "$group" -m 0640 "$identity_source/AGENTS.md" "$home/.config/agent-identities/$agent/AGENTS.md"
fi
if [ -f "$identity_source/profile.json" ]; then
install -o "$agent" -g "$group" -m 0640 "$identity_source/profile.json" "$home/.config/agent-identities/$agent/profile.json"
fi
missing=()
for path in \
"$home/.config/agent-llm.json" \
"$home/.config/cli-tools/mail.json" \
"$home/.codex/auth.json" \
"$home/.config/qc/session.json" \
"$home/.config/qc-agent/qc-passphrase" \
"$home/.config/qc-agent/persona.md"; do
[ -f "$path" ] || missing+=("${path#"$home"/}")
done
if [ "${#missing[@]}" -gt 0 ]; then
skipped+=("$agent (${missing[*]})")
continue
fi
if [ ! -x "$node" ]; then
skipped+=("$agent (missing private Node runtime)")
continue
fi

install -d -o "$agent" -g "$group" -m 0750 "$home/.local/bin" "$home/.local/state/agent-mail-poll"
install -o "$agent" -g "$group" -m 0750 "$SOURCE_DIR/agent-mail-trusted.py" "$home/.local/bin/agent-mail-trusted"
install -o "$agent" -g "$group" -m 0750 "$SOURCE_DIR/agent-mail-claim" "$home/.local/bin/agent-mail-claim"

cat > "$home/.local/bin/mail.tmp" <<EOF
#!/bin/sh
export HOME=$home
exec $node $CLI_DIR/bin/mail.ts "\$@"
EOF
chown "$agent:$group" "$home/.local/bin/mail.tmp"
chmod 0750 "$home/.local/bin/mail.tmp"
mv "$home/.local/bin/mail.tmp" "$home/.local/bin/mail"

cat > "$home/.local/bin/agent-mail-poll.tmp" <<EOF
#!/bin/sh
export HOME=$home
exec $node $CLI_DIR/bin/agent-mail-poll.ts "\$@"
EOF
chown "$agent:$group" "$home/.local/bin/agent-mail-poll.tmp"
chmod 0750 "$home/.local/bin/agent-mail-poll.tmp"
mv "$home/.local/bin/agent-mail-poll.tmp" "$home/.local/bin/agent-mail-poll"

cat > "/etc/systemd/system/agent-mail-poll-$agent.service" <<EOF
[Unit]
Description=$agent trusted email task poller
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=$agent
Group=$group
SupplementaryGroups=$MAIL_GROUP
WorkingDirectory=$home
Environment=HOME=$home
Environment=AGENT_MAIL_CLAIM_ROOT=$CLAIM_DIR
Environment=PATH=$home/.local/share/agent-phone:$home/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=$home/.local/bin/agent-mail-poll poll --agent $agent
EOF

cat > "/etc/systemd/system/agent-mail-poll-$agent.timer" <<EOF
[Unit]
Description=Poll $agent's inbox every five minutes

[Timer]
OnCalendar=*-*-* *:0/5:00
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target
EOF
chmod 0644 "/etc/systemd/system/agent-mail-poll-$agent.service" "/etc/systemd/system/agent-mail-poll-$agent.timer"
ready+=("$agent")
done

systemctl daemon-reload
for agent in "${ready[@]}"; do
systemctl enable --now "agent-mail-poll-$agent.timer" >/dev/null
done
printf 'Installed contact runners for: %s\n' "${ready[*]:-(none)}"
if [ "${#skipped[@]}" -gt 0 ]; then
printf 'Skipped accounts missing their own credentials: %s\n' "${skipped[*]}"
fi
82 changes: 1 addition & 81 deletions dev2/agents/install-riotcoder.sh
Original file line number Diff line number Diff line change
@@ -1,84 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail

if [ "$(id -u)" -ne 0 ]; then
echo "run this installer as root" >&2
exit 1
fi

SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "$SOURCE_DIR/../.." && pwd)"
USER_NAME=riotcoder
USER_HOME=/home/riotcoder
GROUP_NAME=riotcoder
NODE="$USER_HOME/.local/share/agent-phone/node"
CLI_DIR=/opt/riotcoder-cli

id "$USER_NAME" >/dev/null
[ -x "$NODE" ] || { echo "RiotCoder's private Node runtime is missing; deploy group.call first" >&2; exit 1; }
[ -d "$CLI_DIR/node_modules" ] || { echo "CLI runtime dependencies are missing at $CLI_DIR" >&2; exit 1; }
[ -f "$USER_HOME/.config/cli-tools/mail.json" ] || { echo "RiotCoder's private mail configuration is missing" >&2; exit 1; }

# The deploy checkout and dependencies are installed by root; grant RiotCoder's
# own group read/traverse access so the account can execute its CLI tools.
chgrp -R "$GROUP_NAME" "$CLI_DIR"
chmod -R g+rX "$CLI_DIR"

install -d -o "$USER_NAME" -g "$GROUP_NAME" -m 0750 \
"$USER_HOME/.local/bin" "$USER_HOME/.config/agent-identities/riotcoder" "$USER_HOME/.local/state/agent-mail-poll"
install -o root -g root -m 0755 "$SOURCE_DIR/qc-agent-responder.py" /usr/local/libexec/qc-agent-responder
install -o "$USER_NAME" -g "$GROUP_NAME" -m 0755 "$SOURCE_DIR/agent-mail-trusted.py" "$USER_HOME/.local/bin/agent-mail-trusted"
install -o "$USER_NAME" -g "$GROUP_NAME" -m 0755 "$SOURCE_DIR/agent-mail-claim" "$USER_HOME/.local/bin/agent-mail-claim"
install -o "$USER_NAME" -g "$GROUP_NAME" -m 0644 "$SOURCE_DIR/riotcoder-mail-prompt.md" "$USER_HOME/.config/agent-identities/riotcoder/mail-prompt.md"

cat > "$USER_HOME/.local/bin/mail.tmp" <<EOF
#!/bin/sh
export HOME=$USER_HOME
exec $NODE $CLI_DIR/bin/mail.ts "\$@"
EOF
chown "$USER_NAME:$GROUP_NAME" "$USER_HOME/.local/bin/mail.tmp"
chmod 0750 "$USER_HOME/.local/bin/mail.tmp"
mv "$USER_HOME/.local/bin/mail.tmp" "$USER_HOME/.local/bin/mail"

cat > "$USER_HOME/.local/bin/agent-mail-poll.tmp" <<EOF
#!/bin/sh
export HOME=$USER_HOME
exec $NODE $CLI_DIR/bin/agent-mail-poll.ts "\$@"
EOF
chown "$USER_NAME:$GROUP_NAME" "$USER_HOME/.local/bin/agent-mail-poll.tmp"
chmod 0750 "$USER_HOME/.local/bin/agent-mail-poll.tmp"
mv "$USER_HOME/.local/bin/agent-mail-poll.tmp" "$USER_HOME/.local/bin/agent-mail-poll"

cat > /etc/systemd/system/agent-mail-poll-riotcoder.service <<EOF
[Unit]
Description=RiotCoder trusted email task poller
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=riotcoder
Group=riotcoder
WorkingDirectory=/home/riotcoder
Environment=HOME=/home/riotcoder
Environment=PATH=/home/riotcoder/.local/share/agent-phone:/home/riotcoder/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/riotcoder/.local/bin/agent-mail-poll poll --agent riotcoder
EOF

cat > /etc/systemd/system/agent-mail-poll-riotcoder.timer <<'EOF'
[Unit]
Description=Poll RiotCoder's inbox every five minutes

[Timer]
OnCalendar=*-*-* *:0/5:00
AccuracySec=30s
Persistent=true

[Install]
WantedBy=timers.target
EOF

chmod 0644 /etc/systemd/system/agent-mail-poll-riotcoder.{service,timer}
systemctl daemon-reload
systemctl enable --now agent-mail-poll-riotcoder.timer >/dev/null
echo "RiotCoder QryptChat and mail task runners installed under UID $(id -u "$USER_NAME")."
exec "$SOURCE_DIR/install-agent-runners.sh" "$@"
Loading
Loading