Manage Long-Running Agents on Teleport Beams
You can use Teleport Beams to securely manage long-running agentic workloads,
such as complex coding projects. Each beam deploys with a tool, beamctl, that
allows you to manage services that continue running after you exit your SSH
session.
In this guide, you will learn how to manage long-running agentic workloads on a Teleport beam by walking through a small demo project.
How it works
Teleport Beams are micro VM sandboxes for running agentic workloads, hosted on
the Teleport Cloud infrastructure. beam-init is the init system for a Teleport
beam, similar to systemd in that it runs as PID 1 and spawns other processes
that run on the host. beamctl is a CLI for managing processes spawned by
beam-init, which it communicates with over a local Unix socket.
These processes are called services. Unlike systemd, which loads service
definition files from disk, beam-init requires the user to declare new
services while executing them over the command line. This is consistent with the
ephemeral nature of a Teleport beam, which expires after 24 hours.
This guide illustrates the following beamctl commands in action:
| Command | Description |
|---|---|
beamctl start <command> | Create a service. |
beamctl stop <service name> | Terminate a service process. |
beamctl restart <service name> | Terminate a service process and start it again using its original command. |
beamctl list | List all services and their statuses. |
beamctl logs <service name> | Show the output of a service. |
Deleting a service
If you terminate a beam-init service with beamctl stop, the default behavior
is for beam-init to retain the service in its list of managed services. To
remove a service from the list of services beam-init tracks, run the following
command:
beamctl stop --prune <service name>
Otherwise, beam-init does not allow you to start a service with the same name
as an existing one, even if that service is stopped.
For a full list of commands, run the following on your beam:
beamctl help
Prerequisites
This guide requires a Teleport Beams account. Start your free trial.
In the demo project we'll use for this guide, you will run two continuous agent workloads to build a simple web application:
- Agent 1: Runs tests and gets them to pass by writing and optimizing code.
- Agent 2: Writes tests and designs testable function signatures, but leaves implementations up to Agent 1.
Agent 2 writes tests that handle edge cases Agent 1 hasn't addressed yet. This forces the agents to adopt a test-driven development (TDD) workflow and prevents the agent writing the application code from adding trivial tests.
The demo we run in this guide skips permissions in agent prompts. Since an agent on a Teleport beam inherits the permissions of the user who started the beam, we recommend that you authenticate to Teleport as a user with no access to Teleport-protected resources before starting this guide.
Step 1/2. Set up the demo project
The demo project we'll create in this guide lets users enter Morse code using a browser button and converts it to text.
Feel free to use another project idea. If you do, read through the rest of the guide first to understand the relevant workflows.
Create a Teleport beam and set up a project so that your agents have a place to carry out their work:
-
Create a beam:
tsh beams add -
On the beam, set up a project directory:
mkdir morsecodecd morsecode -
Set up your project:
npm init -
Name the package
morsecodeand accept all thenpmdefaults. -
Install Vite, which we'll use to set up the NodeJS project, and
vitestfor testing:npm install -D vitest vite@~8.2.0 -
Add empty directories:
mkdir tests src -
Start a dev server, which reloads automatically when source files change:
beamctl start --name=server -- \ bash -c 'export HOME=/home/beams; \ cd /home/beams/morsecode; \ npx vite --host --port=8080;'Started service serverbeamctl startstarts a serviced managed by thebeam-initinit system, which manages all processes that run on a Teleport beam. Here we set the home directory as the one in which we created our Vite project to ensurenpmcan work as expected.The server must run on port
8080so you can visit it via Teleport later in the guide. -
Copy the following text to a file called SPEC.md on the beam:
# Morse code in your browser When the user taps a button, the app reads it as Morse code and converts text to a readout below the button. There is a collapsable Morse code key as a reference for the user. -
Start the testing agent with
claude, which comes preinstalled on the beam (as doescodex). Every 10 seconds, the testing agent adds new tests to implement the application and address potential edge cases. Like the coding agent we'll start next, the testing agent has a limit of 30 executions, amounting to around five minutes of work:beamctl start --name=tester -- bash -c ' \ export HOME=/home/beams; \ export count=0; \ cd /home/beams/morsecode; \ while [ $count -lt 30 ]; do \ claude -p --dangerously-skip-permissions < /dev/null "\ Read SPEC.md and the existing test files under tests. Identify one \ behavior from the spec that is not covered by an existing test yet, \ e.g., a new function, or an additional edge case for a function \ you have already tested. If a test calls a function that does not exist \ yet, import it in the new test and assume it has a testable function \ signature, but do not create or modify anything in src. Another agent \ will define functions and get tests to pass. Do not modify or remove \ existing tests."; \ sleep 10; \ count=$((count+1)); \ done;'Started service tester -
Start the coding agent:
beamctl start --name=coder -- bash -c ' \ export HOME=/home/beams; \ export count=0; \ cd /home/beams/morsecode; \ while [ $count -lt 30 ]; do \ if ! npx vitest run --silent; then \ claude -p --dangerously-skip-permissions < /dev/null "\ Read SPEC.md and existing test files under tests. Get the tests to \ pass by editing files in src. Maintain a minimal Vite entrypoint at \ index.html to wire everything up."; \ fi; \ sleep 10; \ count=$((count+1)); \ done;'Started service coder -
Make sure all services are running as expected. All should report
running:beamctl listbootstrap (running PID=2) coder (running PID=168) server (running PID=5817) tester (running PID=136)Problem starting a service?
If a service has not started correctly, check its logs:
beamctl logs testerbeamctl logs coderbeamctl logs serverIf the problem has to do with the command you ran to start a service (e.g., a typo), prune the service and start it again with the correct command:
beamctl stop --prune <service>beamctl start <correct command> -
Exit your session.
exitlogoutthe connection was closed on the remote side at <timestamp>
Do something else for five minutes.
Step 2/2. Check your app
At this point, your agents should have made substantial progress on your demo project.
-
Find the beam you created so you can access it:
tsh beams ls -
Publish the application. The command below enrolls your demo project as a Teleport-protected web app. Assign beam-name to the name of your beam:
tsh beams publish beam-name -
Follow the prompts to authenticate to Teleport and visit your app.
-
In your terminal, SSH into your beam:
tsh beams ssh beam-name -
Show the status of all the
beamctlservices you started:beamctl listbootstrap (running PID=2) coder (running PID=168) server (running PID=5817) tester (running PID=136) -
Check the logs of your
beamctlservices. For example, you can see the tester discussing the tests it has added:beamctl logs testerYou can add the
--followflag to attach your terminal's standard input to the service's logs. -
If the services are still running, you can terminate their processes with the following command:
beamctl stop testerbeamctl stop coder -
If any services have stopped, and you would like them to resume their work, you can run the following command to restart them. For example:
beamctl restart coderbeamctl restart testerbeamctl restart server