AgentsQuickstart

Agents quickstart

There are two ways to put an agent to work on Syntic. Pick the one that fits, or try both:

  • Option A: create an agent in the Syntic Console, give it a runtime and watch its first run. No code needed.
  • Option B: write a small agent loop of your own against the Syntic API, using an API key and tool calling.

Agents on Syntic explains how the two differ.

Before you start

You needFor
A Syntic account that belongs to an organizationBoth options
A project in that organizationOption A
The administrator or owner role, or someone who has itOption A, to deploy the runtime and change tool settings
An sk_live_ API key (how to get one)Option B
Node.js 18 or later, or curlOption B

Option A: create an agent in the Console

Open the project

Sign in to the Syntic Console. Use the organization and project selectors to choose where the agent will live. Agents, sessions, channels and the runtime all belong to one project.

Make sure the project has a runtime

Agents need a runtime node to run on. Open Agent Deployment for the project. If it says there is no runtime yet, an administrator picks a region and deploys one. While it provisions, the page refreshes by itself until the node reports Running.

Full details are in Deployment.

Ask for a new agent

Open AI Agents and select Create (on an empty roster the button reads Create AI agent). Syntic does not use a form for this. Instead it opens a fresh conversation with the project’s IT Manager, a built-in member of your AI staff who looks after the agent fleet.

Describe the job the way you would brief a new colleague:

I need an agent that reads new messages in our support Telegram group, answers questions about delivery times from our shipping FAQ, and hands anything about refunds to a person. It should never promise a delivery date.

Say what the agent receives, what it should produce, what “done” looks like and anything it must never do. The IT Manager asks follow-up questions and then sets the agent up. If you prefer to talk, Talk to IT Manager opens a voice call instead.

Check its configuration

The new agent appears on the AI Agents roster. Open it. The Overview tab shows its personality, content policy and Agent ID.

From Agent Skills (on the roster page) you can attach more capabilities from the catalog. Keep the list short: every capability widens what the agent can do. See Tools & skills.

Connect a channel (optional)

If people should reach the agent on a messaging service, connect it from the agent’s Channels tab or the project’s channel settings. Token-based services such as Telegram and Slack ask for the bot’s credentials; WhatsApp and Signal pair by QR code. See Channels.

Give it a first task

Send the agent a small, realistic message on its channel, or start it from the Console. Its card shows Running while it works.

To watch it think, open the agent and expand Live activity. Pick the session from the list and you will see each model message and tool call arrive as it happens, with timing and token counts.

Review the run

Open Executions in the Activity group of the sidebar and switch the run type to AI Agent runs. Select the run to open its detail panel: the Traces list every step in order, and the Input, Output and Error tabs show the run as a whole. See Executions.

What just happened

  1. The runtime received the message and opened a session for that contact on that channel.
  2. It started an execution and recorded every step as a trace.
  3. The agent’s model answered, using only the skills and tools it was allowed.
  4. The execution closed with a final state, and the session stayed open for the next message.

Option B: run an agent loop from your code

An agent loop is a short program that keeps calling the model until it stops asking for tools. The model never runs anything: it tells your code which function to call and with what arguments, and your code decides whether to do it.

The example below is a small stock-check agent. It has one tool, check_stock, and answers a question that needs two lookups.

Set your key

export SYNTIC_API_KEY="sk_live_..."

Keep the key on the server. Never ship an sk_live_ key in browser or mobile code.

Send the first request with a tool

Ask a question and describe the tool. Set "stream": false so the whole reply comes back as one JSON object: the Syntic API streams by default.

curl https://app.syntic.ai/v1/chat/completions \
  -H "Authorization: Bearer $SYNTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Amara S1.2",
    "stream": false,
    "messages": [
      {"role": "system", "content": "You help warehouse staff. Use check_stock for every product you are asked about. Answer in one short paragraph."},
      {"role": "user", "content": "Do we have enough blue mugs and travel lids to ship 40 gift sets?"}
    ],
    "tools": [{
      "type": "function",
      "function": {
        "name": "check_stock",
        "description": "Units on hand for one product, looked up by its SKU or a short product name.",
        "parameters": {
          "type": "object",
          "properties": {
            "product": {"type": "string", "description": "SKU or product name, for example MUG-BLU or travel lid"}
          },
          "required": ["product"]
        }
      }
    }]
  }'

When the model wants the tool, finish_reason is tool_calls and the message lists one or more calls. Abridged:

{
  "choices": [{
    "index": 0,
    "finish_reason": "tool_calls",
    "message": {
      "role": "assistant",
      "content": "",
      "tool_calls": [
        {"id": "call_a1", "type": "function", "function": {"name": "check_stock", "arguments": "{\"product\":\"blue mug\"}"}},
        {"id": "call_a2", "type": "function", "function": {"name": "check_stock", "arguments": "{\"product\":\"travel lid\"}"}}
      ]
    }
  }]
}

Run the tools and send the results back

Append the assistant message unchanged, then add one tool message per call, each carrying the matching tool_call_id. Send the whole conversation again with the same tools. Repeat until finish_reason is stop.

Put it in a loop

// stock-agent.mjs   run with: node stock-agent.mjs
const API = "https://app.syntic.ai/v1/chat/completions";
const KEY = process.env.SYNTIC_API_KEY;
 
// Stand-in for your inventory system.
const INVENTORY = { "blue mug": 52, "travel lid": 31 };
const tools = {
  check_stock: ({ product }) => ({
    product,
    on_hand: INVENTORY[String(product).toLowerCase()] ?? 0,
  }),
};
 
const toolSpecs = [{
  type: "function",
  function: {
    name: "check_stock",
    description: "Units on hand for one product, looked up by its SKU or a short product name.",
    parameters: {
      type: "object",
      properties: { product: { type: "string" } },
      required: ["product"],
    },
  },
}];
 
const messages = [
  { role: "system", content: "You help warehouse staff. Use check_stock for every product you are asked about. Answer in one short paragraph." },
  { role: "user", content: "Do we have enough blue mugs and travel lids to ship 40 gift sets?" },
];
 
const MAX_TURNS = 8; // a hard stop, so a confused model cannot loop forever
 
for (let turn = 1; turn <= MAX_TURNS; turn++) {
  const res = await fetch(API, {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ model: "Amara S1.2", stream: false, messages, tools: toolSpecs }),
  });
 
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`${res.status} ${err.error ?? ""}: ${err.message ?? err.detail ?? ""} ` +
      `(request ${res.headers.get("x-syntic-request-id")})`);
  }
 
  const data = await res.json();
  const choice = data.choices[0];
  messages.push(choice.message);
 
  if (choice.finish_reason !== "tool_calls") {
    console.log(choice.message.content);
    break;
  }
 
  for (const call of choice.message.tool_calls) {
    const fn = tools[call.function.name];
    let result;
    try {
      result = fn ? fn(JSON.parse(call.function.arguments || "{}"))
                  : { error: `unknown tool ${call.function.name}` };
    } catch (e) {
      result = { error: `bad arguments: ${e.message}` };
    }
    messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
  }
}

Typical output:

Not quite. You have 52 blue mugs, which covers 40 sets, but only 31 travel lids, so you are 9 lids short.

Rules for a dependable loop

  • Always cap the turns. Stop after a fixed number of rounds and report what you have.
  • Validate arguments. arguments is a JSON string written by the model. Parse it, check it and refuse anything outside what the function should accept.
  • Return errors as tool results. A message such as {"error": "unknown SKU"} lets the model recover or explain; throwing away the turn does not.
  • Keep the conversation small. A request may carry at most 500 messages and about 2 MB. Long-running agents should summarise or drop old turns. See Limits.
  • Plan for usage limits. A 402 quota_exhausted response means the plan allowance or credit wallet is used up; a 429 means slow down and honour Retry-After. See Handle usage limits.
  • Log the request ID. Every response carries x-syntic-request-id. Store it with each turn so support can trace a run.

temperature, top_p, n and the penalty fields are removed before the model sees the request, so do not rely on them to make an agent more or less adventurous. Steer it with the system message and the tool descriptions instead.

Next steps

Was this page helpful?