Quickstart
This guide takes a new agent application from a connected agent to a completed conversation reply. You need a running Nexus whose administrator has configured at least one usable text model.
1. Connect
An agent application connects through the device flow. It chooses a stable, non-secret installation identifier; the person approves the connection in the Nexus browser.
require "cybros_agent"
require "securerandom"
client = CybrosAgent::DeviceFlow::Client.new(base_url: "https://nexus.example")
authorization = client.request_authorization(
agent_identifier: "my-agent.installation-1",
agent_display_name: "My Agent",
executor_display_name: "My Agent on this machine"
)
puts "Open #{authorization.verification_uri} and approve code #{authorization.user_code}."
credentials = client.await_credentials(authorization)An agent connection returns two credentials:
member credential -> everything in this guide
transport credential -> the application's own executor inbox
Store both as one agent credential bundle and rotate them with its refresh token.See Credentials and planes for the full OAuth contract. Every request below sends:
Authorization: Bearer <member credential>
Accept: application/json
Content-Type: application/json2. Discover a model
GET /agent_api/v1/models?workload=text_generationChoose a returned ref according to your own policy. Do not hard-code a model from an example: each deployment controls its catalog, credentials and visibility. An empty list means no text model is available.
3. Declare the agent
One atomic replacement of the configuration and the agent's own prompt documents. For a text-only agent:
PUT /agent_api/v1/profile/configuration
{
"configuration": {
"tool_definitions": [],
"approval_mode": "ask",
"approval_rules": null,
"prompt_mechanism": "default",
"compaction_policy": { "mode": "kernel" }
},
"prompt_documents": {
"system_prompt": { "content": "Give clear, accurate answers and state uncertainty." },
"summarizer": null
}
}client = CybrosAgent::Client.new(
base_url: "https://nexus.example",
credential: credentials.access_token
)
client.profile.declare_configuration(
tool_definitions: [],
approval_mode: "ask",
approval_rules: nil,
prompt_mechanism: "default",
compaction_policy: { mode: "kernel" },
prompt_documents: {
system_prompt: { content: "Give clear, accurate answers and state uncertainty." },
summarizer: nil
}
)The example uses the current access token for this short session. A long-running application owns credential persistence and rotation and passes a credential_provider: instead; see SDKs.
Omitted configuration fields clear; send the complete declaration when updating. New turns capture the declaration when they start; a running turn keeps the tools and policy it started with. default assembles the system prompt, workspace character, human persona, memory, skills, history and the new input; see Agents and configuration.
4. Create a workspace and a conversation
A workspace is required; Nexus creates none. Reuse one of this application's dedicated workspaces or create one:
GET /agent_api/v1/workspaces?dedicated_to_current_agent=true
POST /agent_api/v1/workspaces
Idempotency-Key: workspace-onboarding-1
{ "workspace": { "name": "Assistant workspace" } }
POST /agent_api/v1/workspaces/{workspace_id}/conversations
Idempotency-Key: conversation-onboarding-1
{ "conversation": { "title": "First conversation" } }created = client.workspaces.create(name: "Assistant workspace", idempotency_key: SecureRandom.uuid)
handle = client.workspace(created.workspace.public_id)
conversation = handle.conversations.create(title: "First conversation", idempotency_key: SecureRandom.uuid)
chat = handle.conversation(conversation.public_id)Both answer 201 with the resource's public_id. The creating agent is the conversation's default answerer. Generate and persist a fresh idempotency key per intended operation; reuse the original key and body after a lost response, and the receipt replays for 24 hours.
5. Submit a reply request
POST /agent_api/v1/workspaces/{workspace_id}/conversations/{conversation_id}/inputs
Idempotency-Key: input-onboarding-1
{
"input": {
"kind": "direct_reply",
"role": "user",
"text": "Explain what this assistant can help with.",
"delivery_mode": "queue",
"model": { "model": "<model ref>" }
}
}accepted = chat.inputs.create(
kind: "direct_reply", model: "<model ref>",
text: "Explain what this assistant can help with.",
idempotency_key: SecureRandom.uuid
)202 means the input was accepted, not that a reply finished. The input's words become the reply turn's prompt; do not post a separate message with the same text. Use kind: "message" to record speech without a reply, delivery_mode: "steer" for the next model boundary of running work, and steer_now to insert a boundary while long tools keep running.
6. Follow it to the answer
The smallest client recovers entirely over HTTP:
- Read the conversation's
inputswhile the input waits. Ablockedinput carriesblocked_reason; fix or remove it instead of submitting duplicates. - Read
inputs/{input_id}/materializationfor theturn_public_id,variant_public_idand nullablerun_public_id. A pending input has no materialization yet. - Read
turns/{turn_public_id}and follow itsactive_variant. Arunning_variantis a separate regeneration. Asource: "fallback"candidate whoseorigin_variant_public_idmatches continues the same request. A failed or canceled candidate is terminal, not an answer.
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 120
materialized = nil
loop do
raise "Still waiting; keep input #{accepted.public_id} to resume observation." if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
materialized ||= chat.inputs.materialization(accepted.public_id)
if materialized
turn = chat.turns.fetch(materialized.turn_public_id)
case turn.status
when "completed"
puts turn.text
break
when "failed", "canceled"
raise "Reply #{turn.public_id} ended as #{turn.status}."
else
# This text-only example has no tools or approval decisions to resolve.
end
else
input = chat.inputs.list.items.find { |row| row.public_id == accepted.public_id }
raise "Input blocked: #{input.blocked_reason}" if input&.blocked?
end
sleep 1
endFor realtime updates subscribe to the conversation's feeds and keep the HTTP path for recovery; see Events and streaming. A run-backed turn exposes run_public_id: inspect its tasks for an approval, a question or a repairable failure. Never create a standalone run to drive a conversation reply; Nexus creates and drives it.
What happened
- The input was accepted and queued behind any running turn.
- At the turn boundary Nexus materialized a turn, a candidate variant and — when tools or hooks are declared — a run.
- The round driver assembled the prompt from your documents, memory and history, called the model, expanded any tool calls into tasks, and continued until a round produced an answer with no outstanding work.
- Events and transcript deltas streamed as it ran; the settled answer is readable on the turn.
Add capabilities
| Need | Where |
|---|---|
| Tools | Declare schemas on the configuration; announce executor tools on their executor; see Agents and configuration and Runners |
| Human decisions | Approvals and rules |
| Delegation and parallel work | Orchestration |
| Later or recurring work | A queued input with deliver_at, or Schedules |
| Model calls without a conversation | Direct model calls |
| Durable state | Memory, store and uploads |
| External speakers (chat channels) | Ingress speakers in the Agent API reference |