Call from a program
How a Solana program opens a run by CPI from its own PDA and reads the model’s output in a later instruction.
The two steps
- In one instruction, your program opens a run by CPI. A PDA of your program signs as the opener.
- In a later instruction, your program takes the run account as an input and reads its output.
Between the two, turns land on the run like on any other. Your program reads the run account directly, so a turn executes only the curta program.
The curta-cpi crate holds both halves: the CPI helpers and the run account reader. It depends on solana-program and curta-core.
Open a run from a PDA
The transaction first creates the run account with a System instruction: at its full size, owned by the curta program, holding its rent plus the budget. The quote gives all three numbers as runAccountSize, runAccountRentLamports and totalBudget, and Fees describes them. The transaction then invokes your program, and your instruction calls open_run with the PDA’s signer seeds.
use curta_cpi::{open_run, OpenRunAccounts, RunParameters};
open_run(
OpenRunAccounts {
opener, // your PDA: signer, writable
model, // a sealed model, writable
run, // the run account created earlier in this transaction
config, // the Config account
metadata, // the model's section zero weight account
system, // the System program
event_authority, // the curta event authority
program, // the curta program
},
RunParameters { max_tokens, temperature_q16, seed, budget_lamports },
prompt, // &[u8], UTF-8
signer_seeds, // the seeds of your PDA
)?;temperature_q16 is the temperature in fixed point with 16 fractional bits: 65536 is 1.0. budget_lamports is the budget the run account holds above its rent.
open_run_instruction builds the same instruction without invoking it.
Read the completed run
Pass the run account and the model account to a later instruction. RunView::read checks that the account is owned by the curta program and is a run, and returns a borrowed view of it.
use curta_cpi::{RunState, RunView};
let data = run.try_borrow_data()?;
let view = RunView::read(run.owner, &CURTA, &data)?;
if view.opener() != my_pda || view.model() != *model.key || view.state() != RunState::Complete {
return Err(ProgramError::InvalidAccountData);
}
let m = model.try_borrow_data()?;
let config = curta_core::Config::decode(&m[72..100])
.map_err(|_| ProgramError::InvalidAccountData)?;
let text = view.text(config)?;| Method | Returns |
|---|---|
| opener() | The address that opened the run. |
| model() | The model the run is on. |
| state() | Running, Starved, Complete or Cancelled. |
| generated() | The count of tokens the run has produced. |
| settled() | Whether the run has settled. |
| text(config) | The output’s text bytes, after checking the working state against the configuration. |
| core() | The run’s working state, which curta-core reads. |
config is the model’s configuration as a curta_core::Config: seven u32 values at byte 72 of the Model account. curta_core::output_tokens returns the output’s token ids from core().
Close the run from the PDA
curta_cpi::close_run(program, opener, run, event_authority, signer_seeds)?;The PDA signs as the opener, and the unspent budget and the rent return to it. The run settles before it closes, and settle takes any signer, so the transaction that calls your closing instruction sends settle first.
cancel_run takes the same arguments and cancels the run from the PDA.
The example program
oracle-of-stories is a complete caller program in three instructions. Its opener is a PDA with the seeds "caller" and the address of a story account, so each story has its own opener.
| Tag | Instruction | Accounts, in order | What it does |
|---|---|---|---|
| 0 | request | authority (signer), story, caller PDA, model, run, config, metadata, System program, event authority, curta program | Opens a run of up to 32 tokens by CPI and records the authority, the run and the model in the story account. Its arguments are those of open_run. |
| 1 | record | story, run, model | Checks that the run is the story’s, was opened by the story’s PDA and is complete, then copies its text into the story account. Anyone sends it. |
| 2 | close | authority (signer), story, caller PDA, run, event authority, curta program, System program | Closes the settled run by CPI and transfers what returns to the PDA on to the authority. |
The story account is 4,224 bytes, owned by the example program. It opens with the tag CURTASTO, and the text starts at byte 128 with its length at byte 108. The request instruction’s CPI, as the program has it:
curta_cpi::open_run(
OpenRunAccounts {
opener: caller,
model,
run,
config,
metadata,
system,
event_authority,
program: curta,
},
parameters,
&args[28..],
&[&[b"caller", story.key.as_ref(), &[bump]]],
)?;A program as opener on the run page
A run opened by CPI shows the calling program as its opener on /runs and on the run’s own page. The list filters by opener, so every run a program has opened is one filter away.