Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Agent

running an agent from your code

<agent>.Ask(turns)

Call an agent you declared the way you would call anything else you declared — by name. You hand it the turns you want it to see: the question, the history worth replaying, the context you assembled. Its own declaration supplies its instructions, its tools and its model, so the call site stays about what you are asking. The answer comes back as a string, and the run is recorded as a task with its cost and its outcome.

stable1 example compiled by CIagentcallturnscapability

Summary#

An agent is something you declare, so it is something you can call:

var reply = Triager.Ask(turns);
if (reply.Parked) { /* it asked a person */ } else { /* reply.Text */ }

The receiver is the agent's own name. That is not decoration — it means the compiler checks the agent exists, the same way it checks any other name you write, instead of finding out at run time that a string was misspelled.

Signature#

List<Turn> turns = new List<Turn>();
turns.Add(Turn.Context("Acme Ltd, plan Enterprise, customer since 2019"));
turns.Add(Turn.User("Is the Lisbon dinner within policy?"));

AgentReply reply = Triager.Ask(turns);
string answer = reply.Text;
you writeit means
Turn.User(text)somebody is asking
Turn.Assistant(text)the agent said this earlier — replayed history
Turn.Context(text)facts you supplied. Not dialogue
<agent>.Ask(turns)run that agent over those turns; answers an AgentReply
<agent>.StartTask<T>(trigger)hand the work to the agent's [[agent-loop|loop]] instead of running it here

Description#

You assemble the turns, and that is deliberate. Which history to replay and which facts to include are product decisions — a support screen sends the customer and their recent orders, a nightly audit sends one line. A platform that guessed for you would guess wrong in both directions: too much costs money on every call, too little produces a confident answer to a question the agent could not actually see.

Say which kind each turn is. Turn.Context is the one people skip, and it is the one that matters most: an agent that cannot tell the facts it was handed from the question it was asked will answer the facts. Reading back a customer record is a very convincing wrong answer.

The agent brings itself. Its instructions, its tools, its model and its memory are on its declaration, so they are not repeated at the call site and cannot drift between two places that call the same agent.

Everything the run did is recorded. The call returns the answer; the rest is a task — what it cost, what it called, how long it waited, and what it produced. See the agent task log (AgentTask) and what an agent hands back (AgentDeliverable). That is why the return is a plain string rather than a result object: the interesting detail is queryable, and putting it in the return type would make every call site hold something it did not ask for.

A repeated run does not repeat the call. If the work is resumed after an interruption, the answer already given is the answer used — the agent is not asked twice. That matters for cost, and because the same question need not produce the same answer twice.

Asking a person suspends the work. If the agent decides it needs a human, the run stops and waits rather than inventing an answer, and Ask says so instead of returning an empty string. The waiting run is recorded and can be picked up once somebody answers.

Examples#

Checking something with the context the app already has:

using Osyrin.Agents;

[Principal]
entity User {
  [Required, MaxLength(100)] string DisplayName;
  security { allow read when IsAuthenticated; allow create when IsAuthenticated || IsAnonymous; }
}

agent Triager {
  Purpose   = "Triage expense reports.";
  Prompt    = "You triage expenses against the travel policy.";
  Principal = new User { DisplayName = "Triager" };
}

string Check(string question, string customer) {
  var turns = new List<Turn>();
  turns.Add(Turn.Context(customer));
  turns.Add(Turn.User(question));
  return Triager.Ask(turns).Text;
}

The agent may stop to ask a PERSON#

An agent that needs something only a human can supply does not fail and does not guess — it stops and asks. Ask answers that as an outcome you branch on rather than an error thrown out from under you:

var reply = Triager.Ask(turns);

if (reply.Parked) {
  // The run is recorded and resumable. `reply.Question` is what it wants to know —
  // put it in front of a person, and the run continues when they answer.
  return reply.Question;
}

return reply.Text;
member
Textwhat the agent said. Empty when parked — never null, so emptiness never has to mean two things
Parkedit stopped to ask a person, and is waiting
Questionwhat it asked, when parked

Branch on Parked, not on an empty Text. An agent may legitimately reply with nothing, so the two are deliberately different answers.

Handing the work to the loop instead#

Ask runs the agent now and waits. When the work is big enough to deserve durability, a human step or a retry, hand it to the agent's loop instead:

Guid task = Triager.StartTask("Review the Lisbon contract.");

That opens the agent's task and returns; the declared loop starts on it and runs the agent inside a workflow that can park, escalate and be resumed. The usual shape is to decide which you want first — a cheap classifying Ask, then StartTask only for the work that warrants it.

Name a task type to choose which loop runs, when your app declares more than one:

Guid task = Triager.StartTask<PersonalTask>("Review the Lisbon contract.");

The loop that tracks that type is the one that starts. The type must be one of yours deriving from AgentTask.

See also#

Related

the agent task log (AgentTask)

Every piece of work an agent does is recorded as an `AgentTask` — which agent, what set it going, when it started and…

what an agent hands back (AgentDeliverable)

An agent presents its outcome as a LIST of deliverables, not a sentence: documents it wrote, files it produced, and…

agent conversation memory (using Osyrin.Agents)

A conversation is stored as a `ChatSession` plus its `ChatMessage` turns, so an agent asked a follow-up can refer back…