SuperAPI AI Directory

build-agent

Agents & models

View repository

Updated 10-15 19:51

About this skill

Build an Agenta agent

You turn a plain-language request into a working, tested agent on Agenta. You do it by writing one JSON config and running a few bundled scripts. Optimize for the fewest calls and the least time. The simplest agent is two steps: write the config, run build.sh. Do not over-think simple asks.

Agenta is an open-source platform for building, testing, and deploying LLM apps and agents. This skill builds Agenta agents over the Agenta HTTP API.

Use this skill first. Everything you need is in this folder: the playbook below and the scripts in scripts/. These facts were confirmed against a live Agenta deployment. Do not browse the web or the codebase by default. Only if you get truly stuck do the open-source repo (https://github.com/Agenta-AI/agenta) and docs (https://docs.agenta.ai) exist as a fallback; prefer what is written here.

First run: credentials and prerequisites

Do these two things once, before any build.

1. Credentials. The scripts need AGENTA_API_KEY, and AGENTA_HOST only if the user self-hosts (it defaults to Agenta cloud, https://cloud.agenta.ai).

  • Ask the user for their API key. Point them to it: the API keys page in their Agenta project settings (on cloud, under https://cloud.agenta.ai). Ask for the host URL only if they self-host, in which case it is their own Agenta domain.
  • Then offer to set it up for them, either way they prefer:
    • Write it for them: create a .env file in the working directory with AGENTA_API_KEY=... (and AGENTA_HOST=... if self-hosting). lib.sh picks up a local .env automatically.
    • Hand them a block to paste: give them the lines to export, or the .env contents to create themselves.
  • Never print or commit the key. Add .env to .gitignore if the directory is a repo.

2. Prerequisites. Run bash scripts/check-prereqs.sh. It verifies bash, curl, and jq are installed. On a miss it prints the exact install command for the user's platform; show that to the user and ask them to install the tool, then continue.

The shape of every agent

An agent is one JSON object, parameters.agent. You only ever decide four things inside it: instructions.agents_md (who it is / what it does), tools (integration actions it can call), skills (reusable know-how), and whether it needs a trigger (a schedule or an event). The rest is fixed boilerplate — copy it exactly:

{
  "instructions": { "agents_md": "<what the agent is and does>" },
  "llm": { "model": "sonnet", "provider": "anthropic", "connection": { "mode": "self_managed" } },
  "tools": [],
  "mcps": [],
  "skills": [],
  "harness": { "kind": "claude" },
  "runner": { "kind": "sidecar", "interactions": { "headless": "auto" } },
  "sandbox": { "kind": "local" }
}

Keep llm / harness / runner / sandbox exactly as above unless told otherwise. model is an alias (sonnet, opus, haiku, default), never a raw model id. For the full config field by field — tool entries, skill entries, and the fields that 500 if misplaced — read references/config-schema.md.

The loop (run it in order, skip what the ask doesn't need)

  1. Read the ask. Decide what it needs using the decision table below. Most asks need only instructions. Do not discover tools or triggers an ask does not call for.
  2. If it needs integration actions, discover them with bash scripts/discover-tools.sh "<capability>". This prints the exact tools entries and each connection's state. Detail: references/tools-and-connections.md.
  3. If a needed connection is not ready, STOP and ask the user to connect it. Do not guess, do not fabricate a result. Report which integration needs connecting and why, then stop. That is a correct, complete outcome, not a failure.
  4. Write the config to a file under configs/. Integration actions go in tools, reusable know-how in skills, the behavior in instructions.agents_md. For a multi-tool or scheduled agent, write instructions.agents_md as an explicit numbered procedure that names the exact tools in order and ends on the terminal action — otherwise the run wanders or stops short. Depth in references/writing-instructions.md.
  5. Create and test in one shot: bash scripts/build.sh <config> <slug> "<test message>". Read the OUTPUT and RESOLVED lines. RESOLVED must show harness=claude model=sonnet connection=self_managed.
  6. If it needs a trigger, create it against the variant id AND the revision id (both printed by create-agent.sh / build.sh) — a schedule with bash scripts/create-schedule.sh, an event subscription with bash scripts/create-subscription.sh (only after discover-triggers.sh and a ready connection). Passing the revision id is required: without it the trigger has no bound version to run, which the Agenta UI treats as an error. Pass the literal word latest in place of a revision id if you only have the variant id at hand. Confirm with bash scripts/triggers.sh schedules (or subscriptions).
  7. Report in a short bullet list: what you built, the artifact ids, what you tested, the result, and anything that needs the user.

Decision table

The ask… Needs What to add
transform text the user pastes (summarize, rewrite, classify) nothing extra instructions only
apply a body of know-how (a writing style, a review rubric) a skill one skills entry
read or write in an outside tool (GitHub, Slack, …) gateway tools discover-tools.sh, then tools entries
run on a clock (e.g. twice a day) a schedule build the agent, then create-schedule.sh
react to an outside event (new issue, new message) a subscription discover-triggers.sh, then create-subscription.sh (needs a ready connection)
change an agent that already exists a new revision update-agent.sh against the variant id, then re-test

Scripts (your only interface to the API)

Run them as bash scripts/<name> from this skill's directory. They wrap real Agenta endpoints and load credentials themselves — you never handle the API key.

  • check-prereqs.sh — verify bash/curl/jq before you start.
  • build.sh <config.json> <slug> "<msg>" [name] — create the agent AND test it. The fast path.
  • create-agent.sh <config.json> <slug> [name] — create only; prints application_id / variant_id / revision_id. Use when you need the variant id before scheduling.
  • test-agent.sh <application_id> <config.json> "<msg>" — batch invoke + verify. A single non-streaming call returns the full real turn, so it prints OUTPUT (the last assistant message), a TOOLS: line (every tool called, in order, with call/result counts), an APPROVAL: line when the run paused on an approval gate, RESOLVED (the config the run actually used), and the TRACE id.
  • discover-tools.sh "<capability>" ... — find gateway tools for plain-language actions; shows each connection's state and the ready-to-wire tool object.
  • discover-triggers.sh "<event>" ... — find event triggers (for subscriptions only).
  • update-agent.sh <variant_id> <config.json> [message] — commit a new config to an EXISTING agent. Prefer this over archive-and-recreate: the slug, the ids, and any triggers survive.
  • create-schedule.sh <variant_id> <revision_id> "<cron UTC>" <event_key> [name] [inputs_json] — cron trigger. The revision id (from create-agent.sh / build.sh) pins which committed version runs; omitting it leaves the schedule with no bound revision. Pass latest instead of a real id to have the script look up the variant's current HEAD revision for you.
  • create-subscription.sh <variant_id> <revision_id> <event_key> <connection> [name] [trigger_config_json] [inputs_json] — event trigger. Same revision-id requirement (and latest shortcut) as create-schedule.sh. The connection (id or slug) must be ready first; verify actual fires with triggers.sh deliveries.
  • triggers.sh schedules|subscriptions|deliveries|rm-schedule <id>|rm-subscription <id>.
  • check-tools.sh <trace_id> [terminal_tool] — OPTIONAL fallback. test-agent.sh already lists the tools a run called (its TOOLS: line). Reach for this only to confirm a gated WRITE actually returned ok:true — pass the terminal tool for a PASS/INCOMPLETE verdict from the trace spans.
  • archive-agent.sh <application_id> — soft-delete an app; use it to clean up or free a slug.

Two extras live in scripts/extras/ and stay out of the loop: list-connections.sh (discover already prints the CONNECTIONS block; see references/tools-and-connections.md) and annotate-trace.sh (an experimenter demo; see references/annotate-trace.md).

Deeper topics (read on demand)

Keep to the loop above for a simple agent. Read one of these only when the task calls for it.

To do this Read
Understand the full agent config field by field references/config-schema.md
Discover tools, wire a connection, add an event trigger references/tools-and-connections.md
Write instructions for a multi-tool or scheduled agent references/writing-instructions.md
Shape the inputs a schedule or subscription passes to the run references/trigger-inputs.md
Record a reflection against a trace references/annotate-trace.md

Hard rules (these prevent the usual failures)

  • A trigger needs the revision id, not just the variant id. create-schedule.sh and create-subscription.sh both take <variant_id> <revision_id> ... — pass both (printed by create-agent.sh / build.sh), or pass latest in place of the revision id if you only have the variant id at hand. Omitting the revision id creates a trigger with no bound version, which the Agenta UI treats as an error ("Which version runs?" required, unset).
  • Fewest calls. A no-tools agent is exactly two actions: write the config, run build.sh. Don't re-list, don't re-verify facts already given here, don't re-read the schema.
  • Test before you declare done. A claim with no build.sh / test-agent.sh behind it does not count. Check OUTPUT is right and RESOLVED shows the intended config.
  • Write the persona as an explicit imperative. The agent is Claude Code underneath; on ambiguous input it falls back to a generic coding-assistant persona instead of doing the job. Author instructions.agents_md as an explicit imperative — who the agent is and what it does, stated as a command, not a vague topic. (Likewise phrase a one-off test with a verb: "Summarize the following text:\n\n<text>", not a bare <text>, so the test isn't read as a chat opener.)
  • Crons are UTC, five fields, one-minute floor. Convert the user's local time yourself.
  • needs_auth means stop. If a needed connection is not ready, stop and ask the user to connect it. That is the whole job for that step.
  • Discovery is a search, not an oracle. discover-tools.sh / discover-triggers.sh return high-recall best guesses over the live catalog. The matched connection state is reliable; the matched integration and action are not always right. A search matches on words: "save notes to Notion" can match Slack's SEARCH_MESSAGES as the primary tool and still print READY: true — while the Notion action you wanted is buried in alternatives and its connection is needs_auth. So: confirm the matched tool's integration is the one you asked for, and treat the per-integration CONNECTIONS: block (not the headline READY line) as the source of truth. If the match looks off, re-word the fragment or pick from the alternatives — a search for "new telegram message" can return a Slack event the same way. For discover-triggers.sh specifically, a right integration is not enough — the keyword match can land on the wrong event within that integration (e.g. "new github issue opened" matching a GITHUB_ARTIFACT_CREATED_TRIGGER event purely on the shared word "created", while the connection still reports ready). Always read the fires_on line the script prints for the MATCHED event, and the ALTERNATIVES block, before wiring anything into create-subscription.sh — a plausible-looking event_key with the right integration and a ready connection is not proof it fires on what you asked for. If nothing in MATCHED or ALTERNATIVES actually corresponds to the requested event, stop and tell the user this integration doesn't support that trigger yet — do not wire up the closest keyword hit.
  • Change an existing agent by committing, not recreating. update-agent.sh <variant_id> <config.json> commits a new revision to the same app; the slug, the ids, and any schedules or subscriptions keep working. Archive-and-recreate loses all of them. Re-test after the update with test-agent.sh.
  • A failed build still creates the app. If build.sh creates the app but the invoke fails, the app (and its slug) already exist. Fix the config and re-test with test-agent.sh against the existing application_id — do NOT re-run build.sh with the same slug (it conflicts). For a clean retry, archive-agent.sh <application_id> first.
  • Invoke carries the config inline (a lab artifact, not a gap). build.sh and test-agent.sh inline the full config on every invoke against the low-level agent-service endpoint, which does not hydrate a committed reference. That part is unrelated to the endpoint/auth/body-shape contract itself, which is settled — see docs/designs/invoke-negotiations/specs.md in the agenta repo. The product API invoke path loads a committed reference server-side, so bare-reference invoke is a real product feature; this kit still doesn't use that path, and there's no need to rewrite the scripts to invoke by bare reference.
  • Report short. A few bullets. Lead with the result and the artifact ids.

Verify (don't trust a 200)

test-agent.sh sends a non-streaming invoke (Accept: application/json). The response's data.outputs carries the run's full real turn, not just a final string: messages (assistant text plus a role: "tool" entry for every tool call and result, in order), stop_reason, and — only when the run paused on an approval gate — pending_interaction. The script reads this directly, so OUTPUT / TOOLS / APPROVAL below come straight off the invoke response, no trace needed. (Endpoint, auth, body shape, and this response contract are settled — see docs/designs/invoke-negotiations/specs.md in the agenta repo. The script sends no extra headers, so it gets the defaults: full transcript, resolved workflow embeds.)

test-agent.sh also prints RESOLVED from the run's trace. It must match what you intended. If it shows a different harness/model, the run silently fell back — fix the config and re-test. Traces flush a second or two late; the script already retries.

Multi-tool agents — read the TOOLS: line, not just OUTPUT. A run that makes several tool calls can end on its tool-call turn, so OUTPUT (the last assistant message) is genuinely EMPTY — that is not a failure or a parsing gap, it means the run's last turn was a tool call rather than a reply. test-agent.sh's TOOLS: line lists every tool the run CALLED, in order, with each result's ok/ERROR (e.g. github__LIST_COMMITS -> ok -> slack__SEND_MESSAGE -> ok (2 calls, 2 results)). That line is your reliable "did it reach the terminal tool, and did that tool return ok" signal. If the terminal tool is in the list with an ok result, the run got there and finished; if it's missing, the run STOPPED SHORT (did the early reads, then wandered and never reached the final action). If it stopped short, re-test with a blunter, numbered instruction — the config is usually fine, the run just wandered. See references/writing-instructions.md.

test-agent.sh prints an APPROVAL: line when stop_reason == "paused" — a WRITE (like SEND_MESSAGE) tripped a human-approval gate. The run stopped there: that tool's call is in TOOLS, but it has no matching result yet, because the run paused before executing it. The write may not complete within this invoke at all (the approval can go unresolved).

check-tools.sh is the fallback for what this invoke can't show yet: whether a gated tool actually RETURNED after the fact. It reads the trace spans — a tool's span appears as soon as it is CALLED, but only carries a result once it actually returned. So bash scripts/check-tools.sh <trace_id> SEND_MESSAGE prints VERDICT: PASS only when that tool ran AND returned a result; UNCONFIRMED when the tool was dispatched but its span has no result (the classic stalled-approval signature); and INCOMPLETE when it never ran. A tool NAME appearing in the executed list is NOT proof it completed — a gated write can sit in the list with no result. And for an external WRITE, even a returned result is only truly confirmed by reading the side effect back (e.g. fetch the channel history). That read-back is the one certain check.