Deer Workflow zelf hosten op een VPS
Installeer Deer Workflow op een Ubuntu VPS met Bun en systemd. Leer hoe u agent-graphs beheert, dependencies vastzet en fouten in event streams effectief opspoort en oplost.
Wat u bouwt
Deer Workflow is een code-first runtime voor agent-graphs: de control flow bevindt zich in een TypeScript-bestand dat u kunt controleren, waarbij een coding agent alleen de onderdelen uitvoert die beoordeling vereisen. Deze handleiding installeert de software op één Ubuntu VPS, voert één voorbeeld-graph headless uit onder systemd en schrijft de machineleesbare event stream naar een logbestand dat u kunt doorzoeken wanneer een uitvoering om drie uur 's nachts faalt.
De onderdelen zijn compact. Bun voert de CLI uit. Eén coding agent CLI, zoals Codex of Claude Code, verricht het modelwerk. Eén vastgezet npm-pakket bevat de runtime. Eén TypeScript-bestand bevat uw graph. Een systemd-service en timer voeren het geheel uit volgens een schema. Het grootste deel van deze handleiding behandelt de onderdelen die daadwerkelijk defect kunnen raken: PATH binnen een systemd-unit, agent-credentials in een sessie zonder login-shell, en het vastzetten van een dependency die voor het eerst werd gepubliceerd in juli 2026.
Visual builder, code of alleen het aansturen van de agent
Een self-hoster die werk automatiseert met een model kiest uit drie vormen, die elk op een andere manier kunnen falen.
Een visual builder biedt een canvas, een bibliotheek met nodes en een gebruikersinterface die toegankelijk is voor niet-programmeurs. Dit is een reëel voordeel, en het aanbod is groot genoeg dat er een volledig overzicht bestaat van self-hosted n8n alternatieven om uit te kiezen. De keerzijde is dat de logica uiteindelijk een JSON-document is dat door een UI is geschreven. De diff van dat document is onoverzichtelijk, waardoor het beoordelen van een wijziging betekent dat u het canvas moet openen in plaats van de patch te lezen.
Het direct aansturen van een agent is de tweede vorm. U beschrijft de volledige taak in een alinea en laat het model de volgorde, de retries en het stopmoment bepalen. Dit werkt totdat het model op een dag anders beslist. Er is geen diff, omdat er geen artifact is: het plan bestond in het gesprek en dat gesprek is verdwenen.
Orkestratie in code is de derde vorm. De volgorde van stappen, de fan-out, de retries en de foutafhandeling zijn standaard TypeScript in git. Het model wordt alleen aangeroepen op de punten waar beoordelingsvermogen nodig is. De keerzijde is dat iemand die code moet schrijven en onderhouden, en een collega die geen TypeScript schrijft, kan deze niet aanpassen.
Wat een graph runtime oplevert en wat het kost
- Controleerbare control flow. De graaf is een bestand. Een wijziging in het retry-beleid is in een pull request zichtbaar als drie gewijzigde regels, in plaats van als een verschoven blok.
- Foutafhandeling in versiebeheer. Wat er gebeurt als stap vier faalt, is vastgelegd, getest en voorzien van een tag samen met de rest van uw infrastructuur.
- Een agent die u kunt wisselen. De runtime levert adapters voor Codex, Claude Code en Pi. Het wijzigen van de agent die een stap uitvoert, vereist slechts één import.
- Een uitvoering die u kunt monitoren. Fasen en gebeurtenissen komen uit de runtime als gestructureerde data, waardoor een headless run een record achterlaat dat u kunt bevragen.
De algemene werkwijze, waarbij u de loop ontwerpt waarin het model draait in plaats van het verfijnen van een enkele prompt, wordt loop engineering genoemd, en een graph runtime is een concrete manier om dit te realiseren. De kosten zitten in de setup: een runtime om te installeren, een agent CLI om te authenticeren, geen interface voor niet-programmeurs en een jonge afhankelijkheid die u in de gaten moet houden.
Het project is nieuw, dus pin de versie
Deer Workflow is MIT-gelicentieerd en is nieuw. Op 19 augustus 2026 bevat de repository 47 commits op main. npm bevat drie gepubliceerde versies: 0.0.1 en 0.1.0 op 26 juli 2026, gevolgd door 0.2.0 op 27 juli 2026. Voor elke versie is een git-tag beschikbaar en in de changelog leest u wat er tussen de versies is gewijzigd. De Unreleased-sectie verwijdert reeds het deer-workflow agent-commando, waardoor main en de nieuwste gepubliceerde versie niet langer dezelfde CLI aanbieden.
Dit is geen reden om het project te vermijden. Het is wel een reden om één specifieke versie te installeren en te weten welke versie u heeft geïnstalleerd.
- Installeer een exacte versie, nooit een bereik.
- Leg die versie vast in dezelfde repository als uw grafieken.
- Voer na elke upgrade uw eigen grafiek eenmaal handmatig uit voordat de timer deze opnieuw uitvoert.
Bun en een agent-runtime installeren
Alles hieronder wordt uitgevoerd als een normale gebruiker met sudo-rechten. Voer dit niet uit als root. De CLI's van de agent slaan inloggegevens op in de thuismap van de gebruiker die is ingelogd, en de systemd-unit moet later als diezelfde gebruiker draaien om deze te kunnen vinden.
sudo apt update
sudo apt install -y curl unzip jq git nodejs npm
curl -fsSL https://bun.com/install | bashHet Bun-installatieprogramma pakt een zip-archief uit, dus unzip moet eerst aanwezig zijn. Het installatieprogramma voegt de PATH-regels toe aan uw shell-profiel. Omdat uw huidige shell dat bestand al heeft ingelezen, moet u een nieuwe shell openen of deze twee regels zelf toevoegen aan ~/.bashrc en de shell opnieuw laden.
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$HOME/.npm-global/bin:$PATH"bun --versionDit drukt een versienummer af. bun: command not found betekent dat de PATH-regel ontbreekt in de shell waarin u zich bevindt, niet dat de installatie is mislukt. Voer ls ~/.bun/bin uit voordat u iets opnieuw installeert.
Nu de agent-runtime. Codex CLI is de standaard en wordt geïnstalleerd via npm. Stel een npm-prefix op gebruikersniveau in, zodat de globale installatie geen root-rechten vereist.
npm config set prefix "$HOME/.npm-global"
npm install -g @openai/codex
command -v codex
codexcommand -v codex hoort een pad onder $HOME/.npm-global/bin af te drukken. Het uitvoeren van codex opent de CLI, waar u inlogt met uw ChatGPT-account. Doe dit nu eenmalig, terwijl u het scherm kunt zien.
Claude Code werkt als een alternatieve runtime en heeft een eigen installatieprogramma.
curl -fsSL https://claude.ai/install.sh | bash
claude --versionEen werkende installatie drukt een versie af zoals 2.1.211 (Claude Code). Voer claude eenmalig uit om in te loggen. Dit is hetzelfde type proces, met dezelfde toegang tot uw bestanden, als elke andere agent die u host. De opmerkingen over accounts en beveiliging in een coding agent op een VPS draaien zijn hier ongewijzigd van toepassing.
Deer Workflow installeren en de exacte versie vastzetten
bun install --global @deerwork-ai/deer-workflow@0.2.0
command -v deer-workflowcommand -v toont het absolute pad, normaal gesproken /home/<your user>/.bun/bin/deer-workflow. Kopieer dit naar een veilige locatie. De systemd-unit kan de verkorte naam niet gebruiken.
Houd de versie in het installatiecommando. Het weglaten van @0.2.0 installeert de versie die op de dag van uitvoering het nieuwst is. Bij een project met 47 commits kan dit de CLI wijzigen onder een timer die door niemand wordt gecontroleerd.
Plaats de grafieken in een git-repository
mkdir -p ~/workflows/logs
cd ~/workflows
git initCodex controleert of het wordt uitgevoerd binnen een git-repository. Daarom bevat CodexAgentConfig een skipGitRepositoryCheck-optie voor gevallen waarin u er geen kunt opgeven. Op uw eigen VPS kunt u dit wel, en dat is aanbevolen: een grafiek is code, en het argument voor het schrijven van orchestratie als code vervalt als de code niet onder versiebeheer staat. Maak nu de map logs aan, omdat systemd deze niet voor u aanmaakt.
Een graaf schrijven
Een workflow is een standaard TypeScript-module. Deze exporteert meta, een object met een naam, een beschrijving en de geordende lijst met fasen, en exporteert de handler als default of als een benoemde run-export. Binnen de handler roept u helpers uit het pakket aan. phase() markeert in welke fase de uitvoering zich bevindt, log() schrijft een voortgangsregel, agent() stuurt één prompt naar de coding agent, parallel() voert een lijst met taken gelijktijdig uit en pipeline() pusht een lijst met items door verschillende fasen.
Sla dit op als ~/workflows/log-triage.ts.
import { agent, log, parallel, phase } from "@deerwork-ai/deer-workflow";
export const meta = {
name: "log-triage",
description: "Groups recent service errors and writes one short report.",
phases: [{ title: "Collect" }, { title: "Classify" }, { title: "Report" }],
exampleArgs: { service: "nginx", hours: 24 },
};
export default async function workflow(args: { service: string; hours: number }) {
if (!args?.service) throw new Error("input needs a service name");
phase("Collect");
log(`Reading ${args.hours}h of logs for ${args.service}`);
const found = await agent<{ patterns: string[] }>(
`Read the last ${args.hours} hours of journalctl -u ${args.service} and list the distinct error patterns.`,
{
sandbox: "read-only",
schema: {
type: "object",
properties: { patterns: { type: "array", items: { type: "string" } } },
required: ["patterns"],
additionalProperties: false,
},
},
);
phase("Classify");
log(`Classifying ${found.patterns.length} patterns`);
const notes = await parallel(
found.patterns.map((pattern) => () =>
agent(`Explain this error and its most likely cause: ${pattern}`, { sandbox: "read-only" }),
),
);
phase("Report");
return agent(`Write a short operations report from these notes: ${JSON.stringify(notes.filter(Boolean))}`);
}Vier details in dat bestand zijn van belang.
schemabij eenagent()-aanroep vraagt om gestructureerde output, en de aanroep retourneert het geparseerde object.found.patternsis een echte array waar de rest van de graaf overheen kan itereren. Zonder schema retourneertagent()een string en bent u proza aan het parsen.sandboxbepaalt wat die stap mag aanpassen.read-onlyblokkeert schrijfacties,workspace-writestaat beveiligde schrijfacties toe endanger-full-accessverwijdert de beveiliging. Dit wordt per aanroep ingesteld, zodat een graaf op grote schaal kan lezen en op één plek kan schrijven.parallel()accepteert functies, geen promises.map((pattern) => () => agent(...))bouwt een lijst met thunks, zodat de runtime bepaalt wanneer elk onderdeel start. Het direct doorgeven vanagent(...)zou elke aanroep starten op het moment dat de lijst wordt opgebouwd.- Een mislukte taak binnen
parallel()wordtnullen de uitvoering gaat door, omdat gedeeltelijke voltooiing volgens het ontwerp is toegestaan.notes.filter(Boolean)is dus geen decoratie: sla dit over en een mislukte vertakking plaatst de tekstnullin de prompt van de volgende stap.
De eenvoudige agent()-helper gebruikt de standaard runtime, Codex. Om één stap naar Claude Code te sturen, importeert u de agent-klasse en roept u deze direct aan.
import { ClaudeAgent } from "@deerwork-ai/deer-workflow";
const claude = new ClaudeAgent({ sandbox: "read-only" });
const summary = await claude.run<string>("Summarise ./report.md in five lines.");Zo ziet een verwisselbare agent er in de praktijk uit: één import en één constructor, waarbij de graaf eromheen ongewijzigd blijft. De --agent codex|claude|pi-vlag op de CLI hoort bij deer-workflow create, die een workflow-bestand genereert op basis van een beschrijving. Deze wijzigt niet welke runtime deer-workflow run gebruikt.
Voer het eenmalig handmatig uit, daarna headless
cd ~/workflows
deer-workflow run ./log-triage.ts --input '{"service":"nginx","hours":24}'Interactief krijgt u een terminalinterface: de fasen van meta aan de ene kant, het live logboek aan de andere kant. Bekijk één volledige run op deze manier voordat u iets automatiseert. Als de agent niet is ingelogd, of als uw invoer niet overeenkomt met de signature van de handler, ziet u dit binnen enkele seconden in plaats van dat u het volgende week in een logbestand moet terugvinden.
Voor automatisering verplaatst u de invoer naar een bestand. Sla ~/workflows/input.json op:
{ "service": "nginx", "hours": 24 }deer-workflow run ./log-triage.ts --input-file ./input.json --print >> logs/run.jsonl--print, de korte vorm -p, schakelt de interface uit en schrijft de event stream naar stdout, één JSON-object per regel. Er gaat in deze modus niets anders naar stdout, dus door de output direct naar een .jsonl-bestand te schrijven, krijgt u een bestand waarin elke regel parseerbaar is.
De event stream, en waarop te filteren om 03:00 uur
Elke regel bevat type, sequence, timestamp, workflowId, depth en scriptPath. De typen zijn workflow:start, workflow:meta, workflow:end, workflow:error, workflow:phase:start, workflow:phase:end en log. Fase-events bevatten phase, de eind-events bevatten durationMs, een log-event bevat message, en een workflow:error-event bevat error met name, message en meestal stack.
Dat is voldoende structuur om de twee vragen te beantwoorden die u om drie uur 's ochtends heeft: is het voltooid, en waar is het gestopt.
grep workflow:error logs/run.jsonl
jq -r 'select(.type == "workflow:error") | .error.message' logs/run.jsonl
jq -r 'select(.type == "workflow:phase:end") | [.phase, .durationMs] | @tsv' logs/run.jsonl
jq -r 'select(.type == "log") | .message' logs/run.jsonlOm een actieve run te monitoren, volgt u het bestand: tail -f logs/run.jsonl | jq -c 'select(.type == "log")'. Eén run schrijft een klein aantal regels, maar het bestand groeit continu. Voeg daarom een logrotate-regel toe voor ~/workflows/logs/*.jsonl zodra de timer enkele weken in gebruik is.
Uitvoeren onder systemd
Gebruik een oneshot service in combinatie met een timer, in plaats van een langlopende daemon. Het proces start, voert de taak uit en stopt. Schrijf /etc/systemd/system/log-triage.service en vervang deploy door uw gebruikersnaam.
[Unit]
Description=Log triage workflow
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=deploy
WorkingDirectory=/home/deploy/workflows
Environment=HOME=/home/deploy
Environment=PATH=/home/deploy/.bun/bin:/home/deploy/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/deploy/.bun/bin/deer-workflow run ./log-triage.ts --input-file ./input.json --print
StandardOutput=append:/home/deploy/workflows/logs/run.jsonl
StandardError=journal
TimeoutStartSec=3600Voer vervolgens /etc/systemd/system/log-triage.timer uit:
[Unit]
Description=Run the log triage workflow every night
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl start log-triage.service
systemctl status log-triage.service
sudo systemctl enable --now log-triage.timer
systemctl list-timers log-triage.timerStart de service eerst handmatig. Een succesvolle uitvoering eindigt met het correct deactiveren van de unit, en logs/run.jsonl toont een reeks gebeurtenissen die eindigt met workflow:end. Schakel pas daarna de timer in. list-timers toont de volgende geplande uitvoering, en Persistent=true zorgt ervoor dat een gemiste uitvoering, die plaatsvond terwijl de server uitstond, alsnog eenmalig wordt uitgevoerd bij de volgende boot. StandardOutput=append: stuurt de event stream naar het bestand en laat de journal voor alle overige zaken, zodat journalctl -u log-triage.service leesbaar blijft.
Waarom werkt de graaf in mijn shell, maar faalt deze onder systemd?
Controleer deze vier punten, in deze volgorde.
De unit kan de binaries niet vinden. systemd leest nooit ~/.bashrc en het standaard PATH bevat noch ~/.bun/bin, noch ~/.npm-global/bin. De unit faalt binnen een seconde en journalctl -u log-triage.service toont dat de uitvoering van de opdrachtnaam mislukt. Daarom gebruikt ExecStart een absoluut pad en daarom vermeldt Environment=PATH= nog steeds beide mappen: de runtime zelf moet codex of claude kunnen vinden wanneer deze een agent-stap start.
De agent kan zijn inloggegevens niet vinden. De agent CLI leest de inloggegevens vanuit de thuismap. Stel daarom User= en Environment=HOME= expliciet in en geef de thuismap op waarmee u bent ingelogd. Een uitvoering die workflow:start bereikt en vervolgens een workflow:error produceert waarvan het bericht afkomstig is van de agent CLI in plaats van uw eigen code, is bijna altijd hiervan het gevolg.
De uitvoering wordt na 90 seconden beëindigd. Voor Type=oneshot past systemd de start-timeout toe op de gehele opdracht; de standaardwaarde is 90 seconden. Een agent-graaf duurt minuten. Het journal registreert Start operation timed out. Terminating., de unit eindigt in een gefaalde status en het logbestand bevat een halve uitvoering zonder workflow:end. TimeoutStartSec=3600 geeft de unit een uur de tijd. Gebruik infinity als u wilt dat de uitvoering nooit op basis van tijd wordt beëindigd.
Relatieve paden verwijzen naar een andere locatie. ./log-triage.ts en ./input.json zijn relatief ten opzichte van WorkingDirectory. Laat deze regel weg en systemd start het proces in /, waar geen van beide bestanden bestaat.
Wat de orchestrator mag uitvoeren
Een orchestrator die agent-stappen op basis van een timer uitvoert, is een proces dat zonder toezicht op uw server draait. Twee beheersmaatregelen zijn van belang, evenals één budget.
De eerste beheersmaatregel is de sandbox bij elke agent()-aanroep. read-only is de juiste standaardinstelling voor elke stap die alleen leest: logs, statistieken of een repository die u samenvat. Stap over naar workspace-write wanneer er daadwerkelijk geschreven moet worden, en houd het beschrijfbare gebied klein met additionalWritableDirectories in plaats van direct naar danger-full-access te grijpen.
De tweede beheersmaatregel is een persoon. Sommige stappen mogen nooit onbeheerd worden uitgevoerd: e-mail versturen, geld overmaken, gegevens verwijderen of productieconfiguraties wijzigen. In een code-first graaf is de drempel eenvoudig te plaatsen, omdat de stap een regel code is. Stop de uitvoering, leg de voorgestelde actie vast, wacht op een menselijke reactie en ga daarna verder. Een goedkeuringsdrempel plaatsen voor agent-acties behandelt dat patroon volledig, en het hoort thuis in elke graaf die door een timer wordt gestart.
Het budget betreft geld. Elke agent()-aanroep is een volledige agent-sessie, en parallel() start er meerdere tegelijk. Een graaf die zich vertakt in twaalf paden voert dus elke nacht twaalf sessies uit, ongeacht of iemand het rapport leest. De metingen en limieten in AI-agentkosten onder controle houden op een VPS zijn direct van toepassing op een geplande graaf.
Lees de changelog voordat u de runtime bijwerkt, installeer de exacte nieuwe versie en voer uw graaf eenmaal handmatig uit met --print. Bij een project dat nog zo jong is, verandert de CLI-interface nog: de sectie Unreleased laat al een commando vallen dat nog wel bestaat in 0.2.0. Een graaf onder een timer is slechts zo betrouwbaar als de versie die u heeft vastgezet en de laatste uitvoering die u daadwerkelijk heeft gecontroleerd.
FAQ
Heb ik Bun nodig, of werkt Deer Workflow ook met Node.js?
Installeer Bun. Het gepubliceerde pakket wijst de deer-workflow binary naar src/cli.ts, een TypeScript-bronbestand, en de documentatie vermeldt Bun als vereiste. Bun voert TypeScript direct uit, waardoor een build-stap overbodig is. Installeer het met sudo apt install -y unzip gevolgd door curl -fsSL https://bun.com/install | bash en bevestig de installatie met bun --version. U heeft nog steeds Node.js en npm nodig als u de Codex CLI via npm installeert.
Waarom werkt mijn workflow in de terminal, maar faalt deze onder systemd?
Dit ligt bijna altijd aan PATH, HOME of de start-timeout. systemd leest uw shell-profiel niet, dus ExecStart vereist het absolute pad naar deer-workflow en Environment=PATH= vereist de map die codex of claude bevat. De agent CLI leest de inloggegevens uit $HOME, dus stel User= en Environment=HOME= in op het account waarmee u bent ingelogd. Daarnaast erft Type=oneshot een start-timeout van 90 seconden; dit beëindigt een agent-run voortijdig en laat Start operation timed out. Terminating. achter in de journal. Stel daarom TimeoutStartSec=3600 in.
Hoe gebruik ik Claude Code in plaats van Codex voor een stap?
De standaard agent() helper gebruikt de standaard runtime, Codex. Importeer ClaudeAgent uit het pakket, configureer deze en roep .run() aan voor de stappen die u door Claude Code wilt laten afhandelen. De --agent codex|claude|pi flag hoort bij deer-workflow create, het commando dat een workflow-bestand genereert op basis van een beschrijving; dit heeft geen invloed op deer-workflow run. Welke agent u ook gebruikt, deze moet beschikken over een eigen CLI die is geïnstalleerd en ingelogd onder dezelfde gebruiker als de service.
Welke versie van Deer Workflow moet ik installeren?
Precies de versie die u heeft getest. Op 19 augustus 2026 is de laatst gepubliceerde versie 0.2.0, van 27 juli 2026, en de repository bevat 47 commits. Noteer @0.2.0, of de versie die actueel is op het moment van lezen, in het installatiecommando. Houd dit nummer bij in git naast uw grafieken en voer na elke upgrade handmatig één grafiek uit voordat de timer deze opnieuw activeert.