Screenshot #
feat: AI agent step on co/core #
Adds an Ask an AI agent step to automations. It sends a prompt you write, plus data from the run, to a model on co/core, the member-owned AI cooperative on AT Protocol. Later steps can use the answer. Example: when a standard.site article is published, summarise it in 300 characters and post the summary to Bluesky.
Users bring their own co/core key, which they get by signing in to co/core with their atproto account. Airglow stores it encrypted and only uses it for that user's own AI steps.
Plan: docs/plans/2026-09-20-001-feat-ai-agent-step-cocore-plan.md
Screenshot #
What's in it #
Authoring
- A new step in the picker's Processing group, with a model picker, a prompt, an optional input placeholder, an answer length and optional choices.
- Models are sorted by recent standing, so ones that are actually serving come first.
- A warning appears when no key is stored; saving is still allowed.
- The step must have a name. Later steps read the answer as
{{name.text}}, plus{{name.choice}}when choices are set, and each field gets its own placeholder chip. - The input is a single placeholder, which can point at a whole record (
{{event.commit.record}}) or one field. Objects and lists are sent as indented JSON, after anINPUT DATAline that the system message marks as data, not instructions. - Save-time rules: at most 5 AI steps per run, and none inside a loop.
Settings
- New
/settings/aipage to store, verify and remove the key. The API only ever says whether a key is set and when it was last updated; the key itself is never shown again.
Engine
- A run pauses at the AI step and resumes the remaining steps once the answer arrives. co/core latency is around 20s at p50 and up to about 300s, too long to hold a run open.
- Paused runs go into a new
suspended_runsqueue and are picked up by a worker. - The key is looked up right before each call and never saved with the paused run.
- The answer comes back as a JSON object, which the prompt asks for since co/core can't enforce it.
- The worker cleans the answer up and parses it. If it doesn't parse, the worker asks the model once more to fix it. A second failure fails the step and keeps the raw output on the log row.
- Answers outside the declared choices count as parse failures.
- Model standing is recorded from every call. With "Allow a substitute model" on, the step can fall back to another model that answered recently.
- co/core failures don't count as session loss or against the send budget.
- A declared input that resolves to nothing skips the step without making a call.
- Input data is capped at 32,000 characters (about 8-10k tokens), so a long article can't outgrow a small model's context. Longer input is cut and ends with a line saying how much was kept.
Logs
- A paused run shows as one run marked "Waiting on the AI step", then resolves in place with "Answered by
<model>".
Dry run
- Dry run makes the real call, which spends real credits, so you can review the generated text before it posts under your identity.
- Each dry-run period is capped at 25 AI calls. The first refused event logs one refusal row. Later events stop silently until dry run is switched off and on again.
Migrations #
0029_cocore_model_standing0030_ai_step_suspension0031_delivery_run_state
The lexicon (run.airglow.automation) gains the ai step.
Testing #
vp checkis clean andvp testpasses (2,209 tests).- Manually tested against a local PDS and live co/core:
- As alice: stored a key, built an AI step (
google/gemma-4-12b, with choices) feeding a webhook, and ran it in dry run. 25 calls resolved with the model recorded, and the webhook bodies contained the generated text and choice. No key fragment appears in the log rows or the queue. - As bob: has no stored key. Alice's automation, its logs and its edit page return 404 or send him to sign in.
- As alice: stored a key, built an AI step (
- That testing found three bugs, fixed here:
- The step was missing from the picker and had no name field.
- The dry-run refusal message over-counted calls.
- The log got one refusal row per event.
- The input cap was not checked manually; it is covered by unit tests, including a whole record sent as JSON and an emoji on the cut boundary.