> ## Documentation Index
> Fetch the complete documentation index at: https://cadenya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Objectives

> Hand an agent a task, watch it work, answer its questions, and rate the result. The objective lifecycle in code.

An [agent](/docs/guides/sdk/agents) sits still until you give it an [objective](/docs/guides/objectives): a message and some data. The agent then works the problem, calling tools and looping until it answers or gets stuck. This page is the objective lifecycle end to end: create, watch, continue, rate.

## What you need

* Your API key in `CADENYA_API_KEY` and a workspace ID in `CADENYA_WORKSPACE_ID`.
* A **published** agent. The [agents guide](/docs/guides/sdk/agents) ships one in five calls. A draft agent refuses objectives with a `400`.

## Create and watch

Creating an objective returns immediately, in `STATE_PENDING`. The work happens in the background. The cleanest way to follow it is the event stream, which pushes each message and tool call the moment it lands.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Cadenya from '@cadenya/cadenya';

  const client = new Cadenya({ apiKey: process.env.CADENYA_API_KEY });
  const workspaceId = process.env.CADENYA_WORKSPACE_ID!;

  const objective = await client.objectives.create({
    workspaceId,
    agentId: 'external_id:support',
    systemPromptData: { company: 'Acme' },
    firstUserMessage: 'A customer says order A-1007 never arrived. What are the options?',
  });

  const stream = await client.objectives.streamEvents(objective.metadata.id, { workspaceId });

  events:
  for await (const event of stream) {
    const data = event.data;
    switch (data.type) {
      case 'assistantMessage':
        process.stdout.write(data.assistantMessage.content ?? '');
        break;
      case 'toolCalled': {
        // CallableTool is a union: a tool, a sub-agent, or a built-in.
        const tool = data.toolCalled.tool;
        console.log('\n[calling', tool?.type === 'tool' ? tool.tool.name : 'a tool', ']');
        break;
      }
      case 'finalized':
      case 'cancelled':
      case 'timedOut':
      case 'error':
        break events;
    }
  }
  ```

  ```bash cURL theme={null}
  OBJECTIVE=$(curl -s "https://api.cadenya.com/v1/workspaces/${CADENYA_WORKSPACE_ID}/objectives" \
    -H "Authorization: Bearer ${CADENYA_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{
          "agentId": "external_id:support",
          "systemPromptData": { "company": "Acme" },
          "firstUserMessage": "A customer says order A-1007 never arrived. What are the options?"
        }' | jq -r '.metadata.id')

  # The stream stays open; break on the terminal event yourself.
  curl -N "https://api.cadenya.com/v1/workspaces/${CADENYA_WORKSPACE_ID}/objectives/${OBJECTIVE}/events:stream" \
    -H "Authorization: Bearer ${CADENYA_API_KEY}" \
    -H "Accept: text/event-stream"
  ```
</CodeGroup>

`ObjectiveEvent.data` is a discriminated union. The `switch` narrows `data` to the interface for that event type, so each case exposes only its matching payload.

<Warning>
  The stream does not close when the objective finishes. Break on a **terminal event type** (`finalized`, `cancelled`, `timedOut`, `error`), never on end-of-stream. A loop with no break holds the connection until the load balancer cuts it at ten minutes. A single connection lives at most ten minutes by design, so a long objective needs the [`Last-Event-ID` reconnect loop](/docs/api-reference/objectiveeventstreamsservice/stream-objective-events).
</Warning>

## The states, and the one that traps people

An objective moves through seven states. Four are terminal.

| State             | Terminal | Means                                |
| ----------------- | -------- | ------------------------------------ |
| `STATE_PENDING`   |          | Accepted, not started                |
| `STATE_RUNNING`   |          | Working                              |
| `STATE_WAITING`   |          | Answered, waiting for your next turn |
| `STATE_FINALIZED` | yes      | Produced its structured output       |
| `STATE_CANCELLED` | yes      | You cancelled it                     |
| `STATE_TIMED_OUT` | yes      | Hit the inactivity timeout           |
| `STATE_FAILED`    | yes      | Errored                              |

`STATE_WAITING` is the trap. It is **not** terminal. An agent that answers your message and expects a reply parks in `WAITING`, and the stream stays open, so a loop that only breaks on terminal events waits forever. When you are driving a conversation, break on the assistant's message and check for `STATE_WAITING`; when you expect a structured result, wait for `STATE_FINALIZED`. An agent with no [`outputDefinition`](/docs/api-reference/agentservice/create-a-new-agent) never finalizes, so it always ends in `WAITING`.

## Continue the conversation

A `WAITING` objective is a live conversation. Send the next turn with `continue`, and the agent picks up with everything it already knows.

```typescript theme={null}
const waiting = await client.objectives.retrieve(objective.metadata.id, { workspaceId });
if (waiting.state === 'STATE_WAITING') {
  await client.objectives.continue(objective.metadata.id, {
    workspaceId,
    message: 'Go ahead and start the refund.',
  });
}
```

`continue` only works on a `WAITING` objective; on a running one it is a `400` (`"objective must be in waiting state to continue"`). After it, stream again, or read the [event log](/docs/api-reference/objectiveservice/list-objective-events), to see what the agent did next.

<Note>
  The stream strips tool result bodies to a bare `toolCallId` so a megabyte of JSON is not pushed through every connection. When you need what a tool returned, read the `toolResult` event from [List objective events](/docs/api-reference/objectiveservice/list-objective-events), or [Get a tool call](/docs/api-reference/objectiveservice/get-an-objective-tool-call-by-id), which returns the full `result`.
</Note>

## Rate the result

After an objective ends, score it. Feedback is a number from `-1.0` to `1.0` with an optional note, and it feeds the [variation's](/docs/api-reference/agentvariationservice/list-variations) selection: variations that score well get picked more often under weighted selection.

```typescript theme={null}
await client.objectives.feedback.create(objective.metadata.id, {
  workspaceId,
  metadata: {},
  data: { score: 1.0, comment: 'Found the order and offered the right options.' },
});
```

Feedback appends, it does not overwrite, so one objective can carry several scores from several reviewers. Each one moves the serving variation's score toward the evidence. This is the loop that lets you run two variations and let the better one win: create objectives, rate them, and weighted selection does the rest.

## Cancel a runaway

If an objective is going nowhere, cancel it. Cancellation is asynchronous; the objective settles into `STATE_CANCELLED` a moment later.

```typescript theme={null}
await client.objectives.cancel(objective.metadata.id, { workspaceId });
```

## Streaming or webhooks

Both carry the same events. Reach for the **stream** when a human is watching, a chat UI or a live log, and for a **[webhook](/docs/guides/webhooks)** when a machine reacts: deliveries retry and record their status, so a job kicked off by a `finalized` event is not lost if your handler blips. Use both on one objective when a person watches the work while a system records it.

## Use your own IDs

Tag an objective with a ticket number at creation and fetch it back by that, so you never store a Cadenya ID:

```typescript theme={null}
await client.objectives.create({
  workspaceId,
  agentId: 'external_id:support',
  metadata: { externalId: 'ticket-4207' },
  systemPromptData: { company: 'Acme' },
  firstUserMessage: 'My order never arrived.',
});

const objective = await client.objectives.retrieve('external_id:ticket-4207', { workspaceId });
```

## Next steps

<CardGroup cols={2}>
  <Card title="Get structured output" icon="brackets-curly" href="/docs/guides/get-structured-output">
    Give the agent an output schema and read a typed result off the `finalized` event.
  </Card>

  <Card title="Approve a tool call" icon="hand" href="/docs/guides/callbacks/approving-a-tool">
    Put a human between the agent and a dangerous action.
  </Card>

  <Card title="Delegate to sub-agents" icon="sitemap" href="/docs/guides/delegate-to-sub-agents">
    Let an objective spawn child objectives and wait on their results.
  </Card>

  <Card title="Stream objective events" icon="tower-broadcast" href="/docs/api-reference/objectiveeventstreamsservice/stream-objective-events">
    Every event type, the reconnect loop, and what the stream strips.
  </Card>
</CardGroup>
