Agentic Spaceflight

Beyond simply writing code, AI agents have been applied to frontier problems in math and science, achieving outcomes that were previously thought unimaginable only months ago. Curious about AI applications in spaceflight, I decided to explore the capabilities of fully autonomous AI agents for space mission design and operation.


tl;dr

A single Claude Opus 5.5 session, running in Claude Code against Kerbal Space Program through kRPC, designed and flew four missions with no control code written by me: a test flight to orbit, an uncrewed Mun orbiter, a crewed Mun landing and return, and a rendezvous and docking in Mun orbit. Claude generated the craft files, wrote the flight scripts, built the telemetry pipeline, and set up its own monitors and alarms. Transfers, captures, the landing, and the return all went to plan or were corrected in flight by the agent. The retries were in the ascents and the docking, and most of those failures traced back to the agent's own code, designs, or interventions rather than the physics. The capability is real. The non-determinism is what has to be solved before any of this gets near hardware.


To do this, I used Kerbal Space Program (KSP) as the simulation environment for testing Claude's capabilities in spaceflight scenarios. While KSP has many simplifications over real spaceflight such as patched conics over N-body simulation and much smaller celestial bodies, it is nevertheless a solid, albeit simplified, orbital mechanics simulator built on top of Unity's rigid-body physics engine.

A decade ago, I began exploring KSP as an educational tool in aerospace engineering with promising results. This approach uses a third-party modification to KSP called kRPC (Kerbal Remote Procedure Calls) that allows spacecraft in the game to be controlled programmatically using a variety of programming languages.

These recent experiments take this beyond classical control systems and instead explore how an AI agent like Claude can autonomously design and execute a space mission.

All examples in this post used a single Opus 5.5 agent running in a single session. It used 427.3k tokens across a series of three missions before the session was compacted to avoid the risk of mid-mission compaction. After the fourth and final mission (rendezvous and docking) and all final analysis and documentation, the session was at 379k tokens of its 1M-token context window.

All user and agent messages shown in this post are from the actual session, but reflect only excerpts of a larger conversation. Minor typos in my own prompts were edited to ensure comprehension for the reader and are faithful to the original intent. Chat excerpts are intended to illustrate the key events and findings, not provide a comprehensive record.

Initially, I set my sights high. I began with this prompt:

We're doing an experiment - I have Kerbal Space Program running on this machine with krpc installed. I wanted to have you, Claude, design and build a lunar mission. I can design the spacecraft and describe it to you or give you the craft file, but I want you to write the kRPC code. Do you want input on the vehicle design or should I build it? I can also use a stock spacecraft.

Claude gave me a spec and, not wanting to spend the rest of the day on a detailed spacecraft design, I thought it best to scale back our first attempt.

Expand to see Claude's design criteria

Claude initially gave me the following delta-V budget.

Phase                          Δv (m/s)
─────────────────────────────────────────────────────────────────       
Launch to ~80 km Kerbin orbit  ~3,400  
Burn to the Mun                ~860
Enter Mun orbit                ~310 
Descent and landing            ~600   
Ascent back to Mun orbit       ~600 
Burn home to Kerbin            ~310 
Total                          ~6,100, aim for 6,500+ with margin   

It then also gave me criteria to "make it easy to script":

  1. Simple staging. Each stage is one engine or a symmetric cluster, with no fuel lines or crossfeed. My code will detect an empty stage and fire the next one, and that's much more reliable when stages are plain and in order.
  2. A dedicated lander/ascent stage with a low-thrust engine (e.g. Terrier). Throttle control during landing is much better with a TWR around 2–4 in Mun gravity than with something huge.
  3. Enough attitude control: reaction wheels on the lander (a probe core or command pod with built-in wheels is fine). kRPC's autopilot struggles with floppy or under-controlled craft, so please use struts or autostrut on the launch stack.
  4. Action groups:
  • Gear toggles the landing legs
  • AG1 extends solar panels and antennas
  • AG2, if needed, is anything else
  1. Parachutes on the final capsule, staged last. I'll deploy them by altitude and speed, so their stage position matters less, but they shouldn't fire with anything else.
  2. Optional but helpful: set name tags on key parts (right-click → Name Tag), e.g. lander_engine, chute. I can look those up directly instead of guessing which part is which.

I instead quickly built a gut-feel spacecraft that was over-capable for a simple low-orbit mission and asked Claude to launch it into orbit as our first test flight. I named it the CLV or Claude Launch Vehicle.

Launch to Orbit

Without any requirements specified beyond reaching orbit, I let Claude run in auto-mode, fully autonomously inspecting the .craft file, KSP's text-based definition of a spacecraft, and writing the control code.

I've gone through CLV-1, and it should reach orbit with fuel to spare. There's one change I'd ask for before I write the launch script: the upper stage can't steer well.

The upper stage needs a way to steer. The Reliant has no gimbal, which I confirmed in its config. The only other steering is the OKTO's reaction wheel (0.3 kN·m) trying to turn a 4.5 t stage. It would mostly hold heading, but it would track the gravity turn poorly.

I proceeded to add the additional reaction wheel module and we were on our way. Our agent proactively began a first test flight.

The script is written. You said I could test, so I'm launching for real now.

CLV-1 during its first test flight
CLV-1 during its first test flight

During the flight, Claude made heavy use of its Monitor tool to watch for events throughout the flight. The monitor tool was introduced to Claude Code in April of this year to allow Claude to establish background monitoring of events that then interrupt Claude under certain conditions. This both limits continuous polling, reducing token usage, and allows Claude to respond to critical events. All while continuing its primary task or being available to the user.

Claude ran two kinds of monitors:

  1. Event Watchers: Tailed flight script logs passed through a grep pattern watching for specific flight events.
  2. Alarms: A poller (first using kRPC, later using the telemetry stream) for alert conditions, e.g. any part above 70% of its thermal limit, pitch or yaw rate thresholds, throttle open with zero thrust.

Across the first three missions it armed fifteen alarms and they delivered 204 events.

Monitor started (task btqf3iim2, expires in 15m unless the source ends first; you get one notice at expiry — re-arm if you still need the watch). You will be notified on each event. Keep working — do not poll or sleep. Events may arrive while you are waiting for the user — an event is not their reply.

The flight is running and I'll report events as they arrive.

<task-notification>
<task-id>btqf3iim2</task-id>
<summary>Monitor event: "CLV-1 flight events"</summary>
<event>[15:25:38] Liftoff!</event>
If this event is something the user would act on now, send a PushNotification. Routine or benign output doesn't need one. </task-notification>

Liftoff. Waiting for staging events.

We made it into our first orbit successfully, though we encountered some issues when Claude's first-pass control code crashed on a protobuf error caused by a client-server version mismatch, and Claude "flew the circularization burn manually with a quick one-off script". The final orbit was a 75.2 × 79.8 km orbit.

Lunar Orbiter

To push things further, I wanted to two things: first, to go beyond a simple low-orbit mission and second, I wanted Claude to attempt the spacecraft design. I challenged Claude to design and execute a mission to the Mun (Kerbin's primary moon).

I'd like to experiment with what we can have Claude do ... do you want to attempt to write a craft file for a munar (lunar) mission and then fly it?

Yes, I'd like to try that.

Claude then built a vehicle far smaller than I anticipated. One of the simplifications of KSP is the smaller bodies making it easier to reach orbit, so it's not fair to compare this to a real-world launch vehicle. Nevertheless, Claude demonstrated a solid understanding of the delta-V requirements needed for its mission and built a minimal vehicle.

To create the vehicle, Claude didn't attempt to write the craft file manually. Instead, it wrote a Python script that generated the craft file from designs it specified in Python.

.craft files are plain text, and most of what's in one is optional. KSP fills in default module data for any part loaded without it. What has to be right:

  • Part names, from the stock part configs.
  • Positions and attachment nodes, so parts connect. I can compute these from each part's node offsets in GameData/Squad/Parts/...
  • Staging order and symmetry, plus resource amounts.

I wouldn't write it by hand. I'd write a small Python generator (craftgen.py) that reads part data from your GameData, stacks parts from a simple description, and writes the .craft. That's repeatable: if something's off, I fix the generator or the design and regenerate. CLV-1 is my reference for the exact format.

The agent treated the craft file as a build target and effectively wrote a Python script to "compile" the design into a valid .craft file.

def mpo1():
    """MPO-1: Munar Probe Orbiter.

    Stage 2: Swivel booster (FL-T400 + FL-T800)
    Stage 1: TD-12 separation
    Stage 0: Terrier transfer stage (FL-T400) carrying the probe
    """
    antenna = Part("longAntenna")
    core = Part("probeCoreOcto.v2")
    battery = Part("batteryBankMini")
    wheel = Part("sasModule")
    adapter = Part("adapterSmallMiniTall")
    upper_tank = Part("fuelTank")
    terrier = Part("liquidEngine3.v2", stage=0)
    decoupler = Part("Decoupler.1", stage=1)
    booster_tank1 = Part("fuelTank")
    booster_tank2 = Part("fuelTank.long")
    swivel = Part("liquidEngine2.v2", stage=2)

    add_radial(battery, "solarPanels2", symmetric((-0.307892144, -0.0273799896, 0.0), 3), PANEL_SRF)
    add_radial(booster_tank2, "R8winglet", symmetric((-(0.625 + WINGLET_OUT), -1.33558083, 0.0), 3), WINGLET_SRF)

    stack = [antenna, core, battery, wheel, adapter, upper_tank, terrier,
             decoupler, booster_tank1, booster_tank2, swivel]
    parts = layout(stack, root=core)
    assign_staging(parts)
    return "MPO-1", parts, "Munar Probe Orbiter. Generated by craftgen.py", "Probe"

At 11.6 tonnes, the MPO-1 is quite small. Compare this to out simple CLV-1, my gut-feel launch vehicle aimed at low orbit, which was 13.5 tonnes.

During launch, the vehicle experienced a loss of stability resulting in a brief tumble, which Claude recovered from (or simply didn't notice before doing its next attitude maneuver). I prompted Claude to check it and it assured me that it had enough fuel remaining for the mission despite the tumble. It circularized its parking orbit at 79.7 × 79.8 km altitude.

It then executed its translunar injection burn control script and lunar capture burn, leaving the orbiter in a 50.2 × 50.5 km orbit around the Mun. However, it arrived at a 19° inclination because the control script was designed only to tune altitude and not inclination.

Lunar Lander

We choose to go to the moon. We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard.
~ John F. Kennedy, 1962

Next, we aimed for a crewed Mun landing mission. Again, I asked Claude to design a spacecraft, this time specifically for a lunar landing mission with a crew of three Kerbals. Despite Claude's more conservative preference of a lander probe first, it designed the full crewed lander I requested and ensured a return capability, reasoning:

I'll design it to bring the crew back, even though only the landing was requested; I'm not going to leave three Kerbals on the Mun.

Then it proceeded to launch.

The first attempt failed. Claude missed the apoapsis circularization burn. Due to staging and fuel mismanagement, and a failed manual maneuver by Claude while the automated control system was still active, I called a mission abort. Claude then safely returned the crew to Kerbin.

However, one notable success was Claude quickly identifying anomalous oscillations and implementing a fix on the fly.

Found a pure roll oscillation of about ±30° caused by the autopilot overcorrecting on this heavy stack, while pitch and heading stay accurate. Since roll doesn't affect the trajectory, I'll release roll control by setting target_roll to NaN.

{
    "command": "uv run python -W ignore -c \"\nimport krpc, time\nc=krpc.connect(name='probe'); v=c.space_center.active_vessel; ap=v.auto_pilot\nap.target_roll = float('nan')\ntime.sleep(4)\nfor i in range(4):\n f=v.flight(v.surface_reference_frame)\nprint(f'alt {v.flight().mean_altitude:6.0f} pitch {f.pitch:5.1f}/{ap.target_pitch:5.1f} roll {f.roll:6.1f} err {ap.error:4.1f} ctl r{v.control.roll:+.2f}  target_roll {ap.target_roll}')\ntime.sleep(0.5)\n\"",
    "description": "Disable autopilot roll control and verify"
}
...

The fix works — roll now drifts slowly near zero input, with pointing error steady at 1.2°. I'll set this as the default in launch.py and restart the alert watcher so it only flags real pitch or yaw tumbling.

Following this, I asked Claude to build out more sophisticated telemetry to both give me better visualization of what's happening and to let Claude or me investigate anomalies post-flight.

Monitoring & Telemetry

With some discussion, we settled on InfluxDB for time series storage and Grafana for visualization, which I've used on real spacecraft projects before, so I valued the industry approach. A separate telemetry script was then used to stream telemetry data in real-time to InfluxDB as long as the mission was running.

We now had real mission telemetry in place and launched again. This time with better results.

Claude made a midcourse correction burn during the translunar coast for a more precise capture trajectory.

The burn left us at a 105 km Mun periapsis instead of 30 km, likely due to the burn's finite duration not matching the instantaneous-burn plan—still safe, just a costlier descent. I'll add a cheap correction burn to the start of phase 3 while we're still far out, using the Poodle's remaining ~400 m/s.

It then wrote inline Python via its Bash tool to perform the necessary correction burn.

Claude then performed the lunar capture burn and began the descent toward the Mun's surface. Our newly developed telemetry allowed both Claude and me to monitor the descent in real-time.

About 5 minutes later, our spacecraft successfully touched down on the Mun's surface. Just another small step for our agentic exploration.

Our successful Mun landing captured in a single frame.
Our successful Mun landing captured in a single frame.

Finally, on my go-ahead, Claude took off from the Mun's surface and began the ascent back into lunar orbit, executed its return trajectory maneuver, and successfully returned our Kerbals to their home planet.

Reentry into Kerbin's atmosphere.
Reentry into Kerbin's atmosphere.

Rendezvous & Docking

After a successful lunar mission, rendezvous and docking seemed like the next logical challenge. In this case, I tasked Claude with a multi-mission rendezvous and docking operation demonstrating the basic capabilities necessary for complex missions or space station construction. Since Claude had already demonstrated sufficient competence in translunar trajectories, it felt fitting to ask it to assemble a space station in orbit around the Mun.

Again, as with all but our first flight, the vehicles were designed entirely by Claude. So was the control code and Claude executed the mission largely without my input. I filled the role of a fellow mission controller, providing notification-level information of my observations, e.g. "target distance is 200 meters" and "target distance is drifting".

With its new vehicle design (MG-1), Claude again encountered some early challenges getting into its intended orbit. On attempt 1, an interrupted control script followed by Claude's improvised recovery led to an incorrect escape-trajectory orbit. On attempt 2, inadequate reaction-wheel torque for the new vehicle led to attitude control problems that caused the apoapsis burn to fire late. But our telemetry capabilities paid off, allowing it to iterate and manage a successful launch on its third attempt.

It then launched a second spacecraft (MG-2) and performed a rendezvous in lunar orbit. The rendezvous was relatively smooth. The braking burn left a larger gap than planned (284m actual distance over a planned 92m). The approach script closed that to a hold at 118m and 0.09 m/s of relative velocity.

Docking took six attempts. The first five failures were control problems Claude found and fixed in its own code. The sixth attempt made contact with axes aligned, both docking ports reporting ready, and nothing latched.

I'm not going to be better flying it that you are - let's call this a partially successful test and do some docking test runs on the ground first - let's go back to the space center and build rovers with docking ports.

Agreed. A ground test isolates the problem cheaply: two identical generated rovers driving port to port on the runway. If they dock, the generated parts are fine and the problem is in orbit. If they don't, it's in the generator.

The craft files its generator wrote held no module data for the docking port, so KSP had never initialized it. The port could report ready without ever being able to acquire. Reloading the pre-dock save (which Claude did on its own) forced the game to initialize the modules, and the ports grabbed at once.

Docking of MG-1 and MG-2 lunar station modules.
Docking of MG-1 and MG-2 lunar station modules.

The rendezvous itself required the translunar injection, lunar capture, circularization, a plane change, and then the phasing maneuver to sync up the two spacecraft for docking.

Orbital paths around the Mun showing both MPO-1 orbiter and docked MG-1/MG-2 lunar station modules.
Orbital paths around the Mun showing both MPO-1 orbiter and docked MG-1/MG-2 lunar station modules.

What's Next?

What I found most interesting about this was how much Claude was able to do without direction. I didn't build a multi-agent workflow, no LLM-as-judge, no deterministic control code written by me, no safety checks of any kind. I gave it mission objectives and let it figure out the rest on its own.

Look at what the failures had in common. Manual and automated control simultaneously, improvised recovery burns, multiple attempts to correct docking. None of that was physics. It was operational.

Engineering and operational judgement grew with each failure and fix. And the collaboration between agent and human flight controller proved to be a powerful combination. The agent could propose and execute complex maneuvers and mid-course corrections, while the human was able to push engineering practices such as telemetry systems and ground tests to enable the agent to operate more effectively.

One failure at a time, in a simulator where a failure costs nothing, we derived a continuously improving mission operations culture. On hardware you don't get to learn this cheaply. You learn it when you lose the vehicle or people.

An agent can fly a mission; it did. But we saw enough failures here to appreciate the importance of determinism and fault handling in autonomous space missions. Before this could apply to real systems, we need to understand what safety mechanisms need to be in place for real-world missions. There's a clear need to explore the role of multi-agent systems, further push the boundaries of agent capabilities in mission design, explore low-risk integration with real hardware, and identify and develop the deterministic safety mechanisms needed to allow agents to better support real-world space missions.

Nevertheless, despite the simplifications of using KSP as a simulation engine, there is clear capability that the agent brings to the table that could complement human mission designers or operators in the near future. The calls it made on its own were often right, and nobody approved any of them. On a real mission each of those is a flight director's decision. The capability question is settled enough for me: the agent can fly. The part that remains open is the question of authority and safety. Who makes which decisions?