How runs work
The phases of a Mangrove run, why each run is one exchange, and how to read its stream.
The flow of a run
The general flow for using GreenPT's Mangrove is:
- Send your question to Mangrove — you get back an agent ID.
- Hold on to that ID. It is how you follow the run's progress and read its answer.
- Mangrove works through the task and answers. If it needs something only you can provide, it says so and stops.
- Want to take it further? Send another question — that starts a new run.
- Repeat :-)
Inside a run
Every run moves through the same phases, and the event stream shows them as numbered steps:
- A forced step, when the request carried something to read. A recording
is transcribed, and an image is looked at, before the model sees your
question — it never has to decide to do either. A document URL is the
exception: the model calls
document_parseitself, for the reason in Case 2. - The agentic loop, where the model calls whichever tools it decides it needs, over at most 20 rounds.
- A final check, where the model is asked whether its answer is complete and whether a tool it has not used would close a gap. It either uses that tool and answers again, or restates the answer it already had.
The final check is why a run emits its answer twice — see Reading a stream without duplicating the answer.
One run, one exchange
Step 4 is worth spelling out, because it is where Mangrove differs from a chat
endpoint you may be used to: a run is a single exchange, not a
conversation. Every run starts from a blank slate and sees only the
question you send it. Nothing on our side threads two runs together, and no
run can be resumed once it has finished — including one that finished by
asking you for something (see Handling
awaiting_input).
The agent ID does not change that. It keeps the run readable until the
expiresAt timestamp the run reports, so you can poll it, stream it, or come
back for the answer later. Reading a run is not resuming it.
Asking a follow-up
A follow-up is a new run, so carry whatever context it needs into the
question — most simply, by quoting the part of the previous answer you want
built on:
curl -X POST https://api.greenpt.ai/v1/agents/runs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"question": "Earlier I was told: \"EU grid carbon intensity averaged 158 gCO2e/kWh in 2026, down 16% from 189 gCO2e/kWh in 2025.\" How does that compare with 2024, and what drove the difference?"
}'Quoting is not only a convenience — it is what keeps a follow-up cheap. Mangrove, handed the earlier finding, does not have to go looking for it again; asked "and how does that compare with 2024?" on its own, it has to rediscover the whole thing, and you pay for that work a second time (see Billing).
Saved values are a different mechanism
Asking Mangrove to remember something — "save this figure as
grid_intensity_2026" — is not conversation history. It writes a value
under a name, scoped to your account, that you can ask for by name on any
later run, and Mangrove decides to reach for it the same way it decides to
search the web. Use it for figures you want to compare across runs; quote
the previous answer for the thread of a discussion.
Reading a stream without duplicating the answer
The last step of every run is the final check described in Inside a
run, and it asks the model to restate its answer in full. So
a run emits its answer at least twice: once when the model first produces it,
once more from the check. Concatenating every text event gives you the
answer two or three times over.
Two ways to read a stream correctly:
- Use
done.finalText. It is the last message the model produced, which is the answer after any correction the check made. Treattextas progress to display while the run is working, not as the thing to keep. - Reset on
step_start. If you are rendering text as it arrives, clear what you have buffered at each new step. Each step's text then stands on its own, and the final check replaces the draft rather than appending to it.
Polling sidesteps this entirely: result on a succeeded run is the same
string as finalText.
Handling awaiting_input
A run that lacks something it needs ends with an input_required event
instead of tool_call/text/done — for example, a question that asks to
summarize a meeting recording without an audioUrl and without enough
question text to be a pasted transcript, where Mangrove asks for the
recording rather than guessing:
event: input_required
data: {"type":"input_required","message":"I can analyze a meeting recording, but I need the audio first. Create a new run with an audioUrl pointing to the recording, or include the transcript text in the question.","expected":[{"name":"audioUrl","description":"URL of the meeting recording to transcribe"}]}There is no resume: start a new run carrying the field named in expected.