The Agent Loop
At the center of the Syntic Agent SDK is a simple but powerful cycle. When you hand Amara a task, it does not answer in a single pass. Instead it works iteratively: it gathers context, decides on an action, carries it out through a tool, checks the result, and then loops again. This continues until the task is finished or Amara determines it can go no further. Understanding this rhythm is the key to reasoning about agent behavior.
The four phases
Every iteration of the loop moves through four phases.
Gather context. Amara reads the prompt, inspects any files or command output already in the conversation, and forms a plan for the next step. Rich context leads to better decisions, which is why the SDK makes it easy to feed in project files and prior turns.
Take action. Amara selects a tool — reading a file, editing code, running a shell command, calling one of your custom tools — and invokes it with concrete arguments. The SDK executes the call and returns the result.
Verify. Amara examines what came back. Did the command succeed? Did the test pass? Did the edit land correctly? This self-checking step is what separates an agent from a one-shot completion.
Repeat. If the goal is not yet met, Amara folds the new information into its context and starts another iteration.
Observing and steering the loop
Because the SDK streams every step, you can watch the loop unfold in real time and intervene when needed.
import { query } from "@syntic/agent-sdk";
for await (const message of query({
prompt: "Fix the failing test in utils.ts and confirm it passes.",
options: { cwd: process.cwd() },
})) {
if (message.type === "assistant") console.log("thinking / acting");
if (message.type === "tool_result") console.log("verified a step");
if (message.type === "result") console.log(message.result);
}import asyncio
from syntic_agent_sdk import query
async def main():
async for message in query(
prompt="Fix the failing test in utils.py and confirm it passes.",
options={"cwd": "."},
):
if message.type == "result":
print(message.result)
asyncio.run(main())Why iteration matters
The loop lets Amara recover from surprises. A failed command becomes new context for the next attempt rather than a dead end. This is what makes agents resilient on messy, real-world tasks — and why controlling tools and permissions, covered later, gives you precise leverage over each turn.