{% comment %} Shared Agents detail content. Included by detail.html, which extends the SDK adapter (scitex_sdk/app/app_shell.html) and fills scitex_app_content. `url_base` is the app mount prefix so the back link and the lifecycle form action are correct at any mount. {% endcomment %}
Fleet {% if agent %}

{{ agent.name }}

{% else %}

Agent not in scope

{% endif %} {% if agent %} {{ agent.host }} · acting as {{ identity|default:"(unidentified)" }} {% endif %}
{% if not agent %} {% else %} {% comment %} Last delivery outcome: lifecycle/message POSTs redirect back here with ?operation=&state=&message=, and this banner is where that delivery status lands — next to the forms that caused it. Only server-minted operation names render (see _delivery_notice), so the query string cannot inject banner copy. {% endcomment %} {% if notice %}
{{ notice.operation }}: {{ notice.state }} {% if notice.message %}{{ notice.message }}{% endif %} {% if notice.dispatch_id %}dispatch {{ notice.dispatch_id }}{% endif %}
{% endif %}
{{ agent.state_label }} {% if cross_host %}cross-host{% endif %}
Runtime
{{ agent.runtime }}
Billing
{{ agent.billing_mode }}
Auth identity
{{ agent.auth_identity }}
Identity source
{{ agent.runtime_identity_source }}
Harness
{{ agent.harness }}
Role
{{ agent.role }}
Engine
{{ agent.engine }}
Model
{{ agent.model }}
Project
{{ agent.project }}
Host
{{ agent.host }}
PID
{{ agent.pid|default:"—" }}
a2a port
{{ agent.a2a_port|default:"—" }}
Turn URL
{{ agent.turn_url|default:"—" }}
Session
{{ agent.session|default:"—" }}
{% if agent.detail.started_at %}
Started
{{ agent.detail.started_at }}
{% endif %}

Runtime activity

Phase
{% if agent.activity.phase.state == "observed" %}{{ agent.activity.phase.value }}{% else %}Unknown{% endif %}
Operation
{% if agent.activity.operation.state == "observed" %}{{ agent.activity.operation.value }}{% else %}Unknown{% endif %}
Turn elapsed
{% if agent.activity.turn_elapsed.state == "observed" %}{{ agent.activity.turn_elapsed.value }} s{% else %}Unknown{% endif %}
Last progress
{% if agent.activity.last_progress.state == "observed" %}{{ agent.activity.last_progress.value }}{% else %}Unknown{% endif %}
Queue
{% if agent.activity.queue.state == "observed" %}{{ agent.activity.queue.value }}{% else %}Unknown{% endif %}
Inference
{% if agent.activity.inference.state == "observed" %}{{ agent.activity.inference.value }}{% else %}Unknown{% endif %}
Tool
{% if agent.activity.tool.state == "observed" %}{{ agent.activity.tool.value }}{% else %}Unknown{% endif %}
Wait
{% if agent.activity.wait.state == "observed" %}{{ agent.activity.wait.value }}{% else %}Unknown{% endif %}

Only runner-published state is shown. Unknown means SAC has no authoritative signal; session logs are not guessed into activity.

Control

{% if agent.state_detail %}

Status: {{ agent.state_detail }}

{% endif %} {% if agent.detail_error %}

Status endpoint unavailable: {{ agent.detail_error }}

{% endif %} {% if can_operate %}

{% if cross_host %} Cross-host control — this action is recorded in the operator audit trail. {% else %} You are an authorized lifecycle operator for this node's agents. {% endif %}

{% if csrf_token %}{% endif %}
{% comment %} Message / steer and the three SAC UI-control keys. Both POST to the agent's OWN bridge endpoint (its published turn_url) — the GUI never constructs a host/port and never drives tmux. Each submit carries a dispatch id so a double-click cannot inject the text twice. {% endcomment %}

Send a message

{% if csrf_token %}{% endif %}

UI control

Positional input to the agent's live terminal — Enter submits, Escape dismisses, C-c interrupts. Nothing else is available.

{% if csrf_token %}{% endif %}
{% for key in control_keys %} {% endfor %}
{% else %}

Read-only. Control requires an identity listed in the lifecycle-operator allowlist{% if cross_host %} — or, for cross-host agents, the cross-host operator allowlist{% endif %}.

{% endif %}

Message thread

{% comment %} The thread the daemon actually stores: the agent's session log tail (GET /agents//tail, summarized + redacted server-side). The console deliberately keeps no operator-message transcript — a delivery is either reported delivered or reported failed (see the banner above and the send form), never parked in the dashboard. So "history" here is the session records, newest last, capped to the recent few; the full tail stays in the session-log card below. {% endcomment %} {% if notice and notice.operation == "message" or notice and notice.operation == "control" %}

Last send: {{ notice.operation }} · {{ notice.state }}{% if notice.message %} — {{ notice.message }}{% endif %}

{% endif %} {% if agent.session_error %}

History unavailable: {{ agent.session_error }}

{% elif agent.session_lines %}
    {% for line in agent.session_lines|slice:"-5:" %}
  • {{ line }}
  • {% endfor %}

Most recent session records. Send a message to steer this agent.

{% else %}

No session records yet — and the console holds no earlier operator messages to show. Send the first one.

{% endif %}

Resources

{% with r=agent.resources %} {% if r.state == "ok" %}
PID
{{ r.pid }}
RSS
{{ r.rss|default:"—" }}
VSZ
{{ r.vsz|default:"—" }}
CPU ticks
{{ r.cpu_ticks|default:"—" }}
Process tree
{{ r.tree_size }} process(es)
{% elif r.state == "namespace" %}

PID {{ r.pid }} is reported by the listener but runs in a separate pid namespace (apptainer isolation) — live CPU/memory cannot be read from the web process, so no value is shown rather than a guessed one.

{% else %}

No process to read (agent not started or stopped).

{% endif %} {% endwith %}

Recent session log

{% if agent.session_error %}

Log unavailable: {{ agent.session_error }}

{% elif agent.session_lines %}
    {% for line in agent.session_lines %}
  • {{ line }}
  • {% endfor %}

Tail (last {{ agent.session_lines|length }} lines), secrets redacted.

{% else %}

No session log recorded yet.

{% endif %}
{% endif %}