Things on my mind

Feeding 15 people on Mars for 500 sols

Building a forecasting simulator and an AI agent harness for a Mars base, with Team Olympus for NASA’s Mars to Table challenge.

Richard Li ·

A huge thank you to

Team Olympus

Led by Luis Jimenez-Chavez

Food bioproduction
  • Luis Jimenez-Chavez
  • Urban Koi
  • Graham Kulig
  • Sean Gallina
  • Denise Caldwell
  • Deep Thakkar
  • Sara Yesufu
  • Alicja Urbanska
Simulation & automation
  • Steven Roche
  • Conor Gagliardi
  • Richard Li
Systems & operations
  • Carol Klingler
  • Colden Rother
  • Chirag Bachani
  • Logan Dosan
  • Mateo Otero-Diaz
  • Elliot Yohan Lim
Human factors & habitat
  • Nicole Lee
  • August Rosen
  • Alexandria Prisecaru
  • Ceren Erdogan
  • Lily Garibyan
  • Kiya Smith
Nutrition & meals
  • Tomoka Tsuzuki
  • Dan Mai
  • Apsara Mitra
Animation design
  • Zaineb Aljumayaat
  • Ian Haut
  • Sagian Shaw
  • Say Wah
Artist's concept of astronauts, habitat modules, and a rover on the orange Martian surface
An artist’s concept of an early Mars base: habitats, a pressurized rover, and the crew who would have to eat there. Credit: NASA.

This summer I got the wonderful opportunity to contribute to Team Olympus, a group of researchers from across the United States designing a food system that could feed fifteen astronauts on Mars for 500 sols.

There’s a renewed interest in longer-term habitation on the Moon and Mars, and one of the many considerations is food. Astronauts on the space station today eat prepackaged meals that cargo ships bring up from Earth, and the station keeps at least six months of food on board.

The problem is that none of this works on Mars. There are no grocery stores, a cargo ship from Earth takes seven months or more, and launch windows open only every 26 months. Packaged space food stays good for one to two years, but food for a Mars mission would have to last about five years. A message to Earth also takes 3 to 22 minutes each way, so the crew can’t count on quick help from home.

EarthMarsA message: 3 to 22 minutes each way15 crew · 500 sols (about 514 days) · no resupplyA cargo ship: seven months or moreand launch windows only open every 26 monthsEarthMarsA message: 3 to 22 minutes each way15 crew · 500 sols(about 514 days) · no resupplyA cargo ship: seven months or moreand launch windows only open every 26 months
Why a Mars base can’t lean on Earth. Numbers from NASA: signal delay, launch windows.

So the base has to grow some of its own food. Then the farm becomes part of life support: the plants take in the crew’s CO₂, use the crew’s water, and need light that runs on the base’s power. The question we had to answer was how a small crew could grow, cook, and recycle its own food for 500 sols, without real-time help from the ground.

Two astronauts on the International Space Station eating red lettuce leaves grown on board
Growing food in space has already started, on a small scale: Kjell Lindgren and Scott Kelly taste lettuce from the station’s Veggie garden in 2015. Credit: NASA.

The challenge

That’s the problem behind NASA’s Deep Space Food Challenge: Mars to Table, run with the Methuselah Foundation. Teams this year had to design how a Mars base feeds its crew, from seed to plate. Our scenario was 15 people for 500 sols. A sol is a Mars day, about 24 hours and 40 minutes, so 500 sols is about 514 Earth days. The rules were:

  • No resupply from Earth.
  • At most half the crew’s calories from food brought from Earth.
  • Nine hours a sol, counting the whole crew’s time together, for all the farming, processing, and cooking.

Out of 113 teams representing 33 different countries, ours placed in the top 40.

A crew member kneeling beside shelves of hydroponic lettuce, herbs, and peppers under grow lights
NASA is rehearsing this for real: a CHAPEA crew is spending a year inside Mars Dune Alpha, a 3D-printed habitat in Houston with a 22-minute delay on messages home. Here James Spicer tends its hydroponic crops in June 2026. Credit: NASA.

Our design

Our design, OLYMPUS, grows the crew’s food, cooks it, and recycles the waste. It’s built as two mirror-image wings, Athena and Ares. Each wing has its own hydroponic crop rooms and its own tanks of algae, fungi, and yeast for protein. Either wing is big enough to feed the whole crew by itself.

The crew starts out eating food brought from Earth. Algae protein from the tanks is ready by sol 5, fungal protein by sol 7, and mushrooms grown on the base’s own waste by sol 15. Grains and other staples take longer. Their first harvest comes at sol 72, and from then on more of the crew’s food comes from Mars than from Earth. Plant scraps and other waste are broken down, and the water and nutrients go back to the crops.

On a normal sol both wings run at 75% of capacity, so together they grow about 150% of what the crew eats. If one wing has a problem, like a contaminated sample or a failed nutrient loop, it’s sealed off for repairs. The other wing ramps up to 100%, so the crew never misses a meal.

Normal solsboth wings run at 75%Ares downAthena ramps up to 100%Athena downAres ramps up to 100%Athena75%Athena100%Athenabeing repairedAres75%Aresbeing repairedAres100%crew quartersalgae, fungi, yeastcrop roomslabNormal solsboth wings run at 75%Ares downAthena ramps up to 100%Athena downAres ramps up to 100%Athena75%Athena100%Athenabeing repairedAres75%Aresbeing repairedAres100%crew quartersalgae, fungi, yeastcrop roomslab
OLYMPUS, drawn from our floor plan: the crew quarters at one end, the lab at the other, and between them Athena and Ares, mirror images on either side of a central corridor. Each wing has its own crop rooms, kitchen, and tanks of algae, fungi, and yeast, and is sized to feed all fifteen crew. On normal days both run at 75%, so together they grow about 150% of what the crew eats. If one wing is sealed off for repairs, the other ramps up to 100% and feeds the crew by itself. Layout and numbers from our concept of operations.

The kitchens cook from a 14-sol menu, and the fresh part of each meal changes with whatever was harvested that sol.

Six astronauts gathered around a table of food inside the space station
NASA has found that months of the same menu can make astronauts eat less. The Expedition 66 crew at Thanksgiving dinner on the space station in 2021. Credit: NASA.

Simulating the base with BioSim

I worked on the team’s software: the simulation of the base, and an AI assistant for the crew.

One of the challenge’s six deliverables is a model of the base in BioSim, a life-support simulator developed by TRACLabs with NASA’s Johnson Space Center. It shows whether a design keeps the crew alive for the whole mission. BioSim runs a mission one hour at a time. Each hour, the crew breathes in oxygen and breathes out CO₂, the crops take in CO₂ and water, and the machines draw power. Everything moves through stores, like the oxygen and water tanks, and each store and connection has a limit. Most of my work was in BioSim and the code built around it.

Adding microbes to the simulator

BioSim already modeled crops, but not the microbes our design relies on for fast protein, so I added two modules. The first is a Spirulina tank. Spirulina is a blue-green alga (often sold as a green powder at smoothie shops) that uses CO₂, water, and light to make oxygen and protein-rich biomass. The European Space Agency’s MELiSSA life-support project has grown it for years to make oxygen and food for crews.

The second is a fermenter that does the reverse. It takes in harvested algae and oxygen, gives off CO₂, and grows yeast or mycoprotein, the fungus-based protein in products like Quorn. When the two share the cabin air, each one gives off the gas the other needs.

Green chains of Spirulina cells under a microscope
Spirulina under a microscope: long chains of cells that photosynthesize like plants and grow fast. Credit: MaCameron, CC0, via Wikimedia Commons.
cabin airshared by everythingSpirulina tankalgae under lampseats CO₂, makes O₂grows protein-rich biomassFermentergrows yeast or mycoproteineats algae and O₂breathes out CO₂top-upwater and nitrogenalgae storeharvested algaefood storeproteincrew’s mealsCO₂O₂ ×1.35O₂CO₂algaefeedsO₂CO₂foodwhere the loop is opentop-upwater and nitrogenSpirulina tankalgae under lampseats CO₂, makes O₂grows protein-rich biomassalgae storeharvested algaecabin airshared by everythingFermentergrows yeast or mycoproteineats algae and O₂breathes out CO₂food storeproteincrew’s mealsO₂ ×1.35CO₂O₂CO₂algaefeedsO₂CO₂foodwhere the loop is open
The two modules I added to BioSim, on the left and right. Gas moves through the shared cabin air and food moves through the stores. The only things that enter or leave the loop are the crew’s meals and the water and nitrogen added to top it up.

Both modules keep track of every gram of carbon, so none appears or disappears in the simulation. Their rates come from published measurements. The ×1.35 on the oxygen arrow is one example: Spirulina grown on nitrate gives off about 1.35 molecules of O₂ for every molecule of CO₂ it takes in, not the 1.0 from the textbook photosynthesis equation. Using 1.0 would undercount the crew’s oxygen by about a quarter.

Making runs repeatable

Another issue was that BioSim gave different results for the same setup. In one test the simulated crew died at hour 552 on one run and hour 624 on the next. We couldn’t tell whether a change to the design helped or the run just came out differently.

The cause was six random number generators, each started from the computer’s clock, so no two runs began the same way. Two of them were also shared by every simulation on the server, so a run’s results depended on what had run before it. I changed it so each simulation starts from one fixed number, called a seed, and every part of the base gets its own generator made from it. Now the same setup plays out the same way every time. None of the roughly 120 places in the code that draw random numbers had to change.

BeforeAfterclock-seededsix generators, two of themshared by every simulationone seed per rungives each part of the baseits own generatorrun 1crew died at hour 552run 2crew died at hour 624run 1same hourrun 2same hoursame setup, different endingsame setup, same endingBeforeclock-seededsix generators, two of themshared by every simulationrun 1crew died at hour 552run 2crew died at hour 624same setup, different endingAfterone seed per rungives each part of the baseits own generatorrun 1same hourrun 2same hoursame setup, same ending
Why the same setup used to end differently. The random number generators were seeded from the clock, and two were shared by every simulation, so a run depended on whatever had run before it. Now each simulation starts from one seed, so a run can be replayed exactly.

Forecasting a few sols ahead

BioSim stands in for the base’s sensors, so I treated its output like real sensor data. Every reading carries its unit, its timestamp, and its source. A missing reading is reported as missing instead of filled in with a guess. Simulated numbers are labelled as simulated, so they can’t be logged as real measurements.

Knowing what’s in stock now isn’t enough, though. The crew needs warning before something runs out, so I built a forecaster, which we called future-sight. It copies the base’s current stock levels into a separate copy of the simulation and runs that copy forward, up to about 80 sols. Then it reports which stores will run low. The live simulation keeps running untouched.

nowkeeps running, untouchedlive habitatthe running simulationcopy current stock levelssandbox copyfast-forwarded+1 sol = 24 hours+N solswhat runs loweach store: now vs. N sols aheadlive habitatthe running simulationnowkeeps running, untouchedcopy current stock levelssandbox copyfast-forwarded+1 sol = 24 hours+N solswhat runs loweach store: now vs. N sols ahead
Future-sight never touches the live simulation. It copies the current stock levels into a sandbox copy built from the same setup, fast-forwards it an hour at a time, and reports what runs low, labelled as a projection rather than a reading. For now it copies only stock levels, so crops in the copy start from the original setup instead of where they are now.

Checking a meal plan before it runs

I also fixed bugs in marsfood, the team’s tool for comparing base designs: how much to grow, how much to ship from Earth, and how well the crew would eat. One example: the ingredient data listed omega-3 in milligrams while the nutrition targets were in grams, so every omega-3 comparison was off by 1,000 times.

Then I built marsops, the program the crew would use to run the pantry and plan meals. It keeps a log of every change to the pantry, which can be added to but never edited. It plans meals from what’s actually in stock, and it has four screens: Today, Inventory, Production, and Risks. It uses only Python’s standard library and no AI, so it keeps working even when wallEat, the AI assistant I describe below, is turned off.

A wrong meal plan could use up food the base needs later or leave the crew short on nutrients. So marsops doesn’t release a plan just because the planner produced it. Right before a plan goes to the kitchen, marsops builds it again and rechecks it. If any check fails, the plan is blocked.

proposed planfrom the plannerchecked again· regenerates the same plan· enough food in stock· every gram accounted for· meets nutrition targets· fits the crew’s hours· limits measured, not guessed· matches the approved design· nothing changed sincereleasedcooking can startblockedand it says which check failedoverridenames the risks it acceptsand a backup procedurepassany failon the recordproposed planfrom the plannerchecked again· regenerates the same plan· enough food in stock· every gram accounted for· meets nutrition targets· fits the crew’s hours· limits measured, not guessed· matches the approved design· nothing changed sinceblockedand it says whichcheck failedreleasedcooking can startoverridenames the risks it acceptsand a backup procedureany failpasson the record
A failed check blocks the plan and names the check. An override has to say exactly which risks it is accepting, so the record shows what was decided.

wallEat

The second thing I worked on was wallEat, an AI assistant for the crew. With messages to Earth taking up to 22 minutes each way, the crew can’t wait on mission control for every question. Instead, a crew member can ask wallEat things like “what’s on the meal plan this sol, and why?” or “which crops are ready to harvest?” It runs Gemma, Google’s open model, on the base’s own computers, so it works without a connection to Earth. It answers from the mission documents and the simulator’s live readings. I started from a basic framework for running a local model and rewrote most of it over the summer.

Getting the model to use tools, like looking up the pantry or searching the documents, was the easy part. Most of my time went into the harness, the code around the model that decides what it sees and what it’s allowed to do:

  • Six specialists. Instead of one assistant with every tool, wallEat has six roles: a help desk, a crop adviser, a cook, a scheduler, a meal-plan explainer, and future-sight. Each has its own instructions and only the two to seven tools it needs. The 12-billion-parameter model I used did much better this way.
  • Permission checks at run time. The model can ask for a tool it was never given, so listing the allowed tools in the prompt isn’t enough. wallEat checks the role’s permissions right before running any tool and logs every refusal.
  • Documents are data. Text from documents is fenced off and labelled as data. If a document says “ignore previous instructions,” the model treats that as something the document says and reports it, instead of following it. Sources are also ranked by trust: approved procedures first, then reference material, then past sensor readings, then chat memory. Only an approved procedure can back an instruction to the crew.
  • Context budget. The model can only read so much text at once. Once a conversation gets too long, wallEat summarizes it. Passages from documents go in whole or not at all. Tool output that runs over budget is cut off, with a note telling the model it’s only seeing part of it.
  • Saying when something is down. If the model, the simulator, or the document index is unavailable, wallEat says which one is down instead of answering anyway. The check that reports whether wallEat is ready also fails whenever a service it needs is missing.
a questiona specialistone of six roleswith its own toolswhat it readsapproved procedures,then reference material,then old sensor readings,then chat memorylocal AI modelGemma, on the base’sown computerspermissionchecked right beforeeach tool runstool runsdocs, sensors, plansresult read as datarefusal loggedevery timehealth checkreports “not ready” whenever a service it needs is missingwants a toolallowedresultdenieda questiona specialistone of six roleswith its own toolswhat it readsapproved procedures,then reference material,then old sensor readings,then chat memorylocal AI modelGemma, on the base’s own computerspermissionchecked right before each tool runsrefusal loggedevery timetool runsdocs, sensors, plansresult read as datahealth checkreports “not ready” whenever a serviceit needs is missingwants a toolallowedresultdenied
How wallEat answers one question. It goes to one of six specialists, and permission is checked right before each tool runs.

The rule behind all of this is that wallEat can read the pantry, the meal plan, and the simulator, but it can’t change them. Every change is made in marsops by a signed-in crew member who confirms it. I wrote tests that try to break this: the model calling tools it wasn’t given, documents with hidden instructions, file paths that point outside the document folder, and old sensor readings used to answer questions about right now.

crewthe AI assistant, reads onlywallEatanswers questionsreads, never writespermission-checkedevery change confirmedmarsopsno AI insidesigned-in crew memberconfirms each changemission documentsapproved procedureshabitat simulationsensor-style readingswhat-if copiesfood plan and pantryplans, inventory, risksrebuilt from the logchange logappend-only, in orderasksconfirmsread onlyreadingsreadschangechanges thingsonly readscrewthe AI assistant,reads onlywallEatanswers questionsreads, never writespermission-checkedevery changeconfirmedmarsopsno AI insidesigned-in crew memberconfirms each changechange logappend-only, in ordermission documentsapproved procedureshabitat simulationsensor-style readingswhat-if copiesfood plan and pantryplans, inventory, risksrebuilt from the logasksconfirmsread onlyreadingschangereadschanges thingsonly reads
What wallEat can reach. It reads the mission documents, the habitat simulation, and the food plan, but every change goes through marsops.

What I’m taking away

When I started, I thought of an AI agent as a model plus some tools. Now I think most of the work is in the harness around the model: permissions, which sources to trust, what fits in memory, and what happens when something fails. The model is one part inside it, and not a very reliable one. That’s the biggest thing I learned this summer.

The other thing I learned was to double-check my own tools. At one point an automated code audit proposed deleting about 7,300 lines across two repos. When I checked each claim one by one, twelve of the fifteen proposed deletions turned out to be wrong. I built the same habit into the code: when a nutrition label can’t be converted safely, the simulator sets it aside instead of guessing. I’d rather it tell me it doesn’t know than hand me a wrong number.

Thank you to everyone on Team Olympus. I really appreciate getting to work on this with you all!