Author an Agent
Build a typed Agent package with model calls, durable state, Tools, Resources, analytics, and explicit limits.
Before you begin
Choose a stable Agent id. Version labels are optional and may be reused when deploying changed code. Configure a logical Model Resource, list every external capability the Agent needs, and identify the Policy boundaries and budget appropriate for its work. Run npm install @constal/sdk in an ESM package.
Steps
- Export
agent()from the manifest entrypoint. A script Agent providesonMessage; a durable Agent providesinit,step, andoutput. - Set
modelto a logical manifest binding name, not an external provider model id. - Use
ctx.turn()for model work. Pass only the context the current turn needs and enable streaming only when the caller can consume it. - Use
ctx.resourcesplusctx.invoke()or a declared Tool for external operations. Never use arbitrary CRNs from message input and never put Credential material in code, prompts, or manifests. - Use
ctx.commit(),ctx.await(),ctx.spawn(),ctx.step(), and ledger operations for durable workflow behavior. Every external effect belongs behind a governed Resource operation with a truthful effect and recovery contract. - Declare custom analytics with
analyticsEvent()and register every declaration in the Agent definition before emitting it. - Add
constal.agent.jsonwith matching id, entry, mode, labels, fixed or scoped bindings, attached Policies, exposed Tool names, limits, and an optionaluiblock. Version is optional. Add achannels.constal.ai/*label only when the Agent should be eligible for public Channel exposure. Auiblock (id,displayName,source,channel, and optionalaccess,execution,limits,labels) makesconstal deploypackage thesourcedirectory and publish auiResource pinned to the exact Agent revision it built; the deployment receipt lists it underoutputs, only immediate rollouts accept it, and the Agent and its UI are promoted together or not at all. See Deploy an Agent with a UI and Publish a UI with the Agent.
import { agent } from "@constal/sdk";
export default agent({
id: "triage", model: "model",
async onMessage(message, ctx) {
const turn = await ctx.turn({
system: "Classify and resolve the request using only offered capabilities.",
objective: message,
tools: ["lookup_ticket"],
});
return turn.message.content;
},
});Declare setup requirements
Add setup.requirements to the Agent manifest to describe its configuration prerequisites. Setup clients can use this metadata to build a guided flow. For example, this manifest fragment requests a reusable GitHub account and a Channel that delivers GitHub events:
{
"setup": {
"requirements": [
{
"id": "github",
"title": "GitHub access",
"resourceKind": "credential",
"selector": { "matchLabels": { "integration": "github" } },
"binding": { "key": "github", "owner": "principal" }
},
{
"id": "github-events",
"title": "Configure GitHub events",
"resourceKind": "channel",
"selector": { "matchLabels": { "integration": "github" } }
}
]
}
}A setup client should offer matching, available credentials from namespaces the operator can access and persist one explicit choice using the ordinary binding API. github is a logical slot key, not a stored Credential name. Creating a Credential through POST /v1/namespaces/:namespace/credentials accepts labels, so a GitHub credential can carry { "integration": "github" }. Reusing a credential does not require repeating provider authorization or copying secret material; normal platform authorization still applies.
Declare binding.owner explicitly: principal selects the caller's binding, tenant selects shared app/service automation authority, and customer selects the authenticated customer. Setup and runtime use the same resolveBindingOwner helper. There is no fallback between owners. A consuming Resource uses the matching { "kind": "scoped", "key": "github", "owner": "principal", "required": true } in its Credential slot. Bindings are tenant-isolated and namespace-addressed: multiple Agents can reuse the same owner/key assignment, and assignments in other namespaces can reference the same authorized Credential. Changing a shared assignment affects all its consumers. Previously stored declarations without an owner retain their original tenant semantics; new declarations must specify it.
Requirements use the existing Resource selector syntax, including matchExpressions and protected identity labels such as constal.ai/name. Setup clients should check that a matching Channel's target selector includes this Agent. Set optional: true to allow skipping a requirement. Configuration is saved by the ordinary Resource and binding APIs, so a setup client can derive progress from current state. These are configuration prerequisites; runtime access remains governed by actual bindings and Policies. Service-specific provisioning and verification can use the existing constal.setup.v1 companion workflow.
Resource setup requirements use resourceKind and selector. They do not accept contract-name lookups. Credential requirements additionally require one binding.key and binding.owner; arrays of credentials, embedded Credential names, and duplicate owner/key pairs are rejected.
Setup clients can call credentialSetupState(requirement, scope, inventory, binding) from @constal/sdk, using their authorized non-secret inventory (crn, labels, available) and the existing binding head. It returns missing when no credential matches, choose when an explicit selection is needed, ready for a saved available selection, and blocked when the saved selection cannot be used. It never chooses the first match or switches away from an unavailable saved credential. Token rotation preserves the choice because it follows the Credential identity, not a material-version hash. The helper projects state; the ordinary binding API remains responsible for authorization and saving a choice.
For a custom durable setup workflow, credential-interaction panels can opt into receipt: "reference" or receipt: "evidence". Add selection: { selector, binding: { key, owner } } to offer and save an explicitly scoped Credential choice, and configuration for non-secret provider creation settings. Console uses the normal Credential lifecycle, evidence, and binding APIs, including cross-namespace reuse. The resulting constal.setup.credential receipt contains a Credential reference, optional provider evidence, and the confirmed binding revision. Interpret provider claims in Agent code; the receipt never replaces authorization at the point of use. Use setupCredentialReceipt(submission.values, screen.current) after validating the submission with setupSubmission and handling secondary actions.
Verify
Type-check the project, deploy it, and start a controlled Run. Confirm the detail page shows the intended version, mode, logical bindings, Policies, Tools, and limits. Inspect the Run journal to verify every model and Resource operation is pinned and accounted.
Next steps
Choose a proven composition from the Agent patterns catalog, then read Durable Agent patterns, Deploy an Agent, and Resources and Tools.