architecture v3: single VM for all agents + Colony

- One e2-standard-4 (4 vCPU, 16GB) instead of one VM per agent
- Agents as isolated Linux users with separate systemd services
- Birth is fast (~30s) — no VM provisioning, just create user + copy files
- Stagger pulse intervals to avoid resource contention
- systemd MemoryMax per agent (4GB cap)
- ~$50/month total instead of $100+

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 22:15:13 +02:00
parent f88c385794
commit 64034ea60e
3 changed files with 92 additions and 68 deletions

View File

@@ -208,6 +208,7 @@ export default function App() {
) : (
messages.map((msg, i) => {
const prev = i > 0 ? messages[i - 1] : null;
const next = i < messages.length - 1 ? messages[i + 1] : null;
const sameSender = prev && prev.user.username === msg.user.username;
const withinWindow = prev && (new Date(msg.created_at).getTime() - new Date(prev.created_at).getTime()) < 5 * 60 * 1000;
const prevDate = prev ? new Date(prev.created_at).toDateString() : null;
@@ -216,6 +217,12 @@ export default function App() {
// Don't compact: after date break, typed messages (non-text), or replies
const isTyped = msg.type !== "text";
const compact = !!(sameSender && withinWindow && !msg.reply_to && !showDate && !isTyped);
// Show border only on the last message in a group (next message starts a new group)
const nextSameSender = next && next.user.username === msg.user.username;
const nextWithinWindow = next && (new Date(next.created_at).getTime() - new Date(msg.created_at).getTime()) < 5 * 60 * 1000;
const nextDate = next ? new Date(next.created_at).toDateString() : null;
const nextCompact = !!(nextSameSender && nextWithinWindow && !next?.reply_to && nextDate === thisDate && next?.type === "text");
const lastInGroup = !nextCompact;
return (
<div key={msg.id}>
@@ -231,6 +238,7 @@ export default function App() {
<MessageItem
message={msg}
compact={compact}
lastInGroup={lastInGroup}
replyTarget={msg.reply_to ? messagesById.get(msg.reply_to) : undefined}
currentUsername={getCurrentUsername()}
selected={selectedMessages.some((s) => s.id === msg.id)}