Ambient Platform Data Model Spec

June 9, 2026 · View on GitHub

Date: 2026-03-20 Status: Active Last Updated: 2026-06-03 — added Application (GitOps continuous sync for agent fleets); addressed review feedback: credential_id FK for remote auth, RoleBinding escalation rules, prune safety, health status semantics, gitops role grantability, sync engine kind filtering Previous: 2026-05-12 — migrate Credentials from project-scoped to global routes (/credentials); remove project_id from model, OpenAPI, and SDK; add drop-column migration; update coverage matrix Workflow: ../../workflows/sessions/ambient-model.workflow.md — implementation waves, gap table, build commands, run log Design: credentials-session.md — full Credential Kind design spec and rationale


Overview

The Ambient API server provides a coordination layer for orchestrating fleets of persistent agents across projects. The model is intentionally simple:

  • Project — a workspace. Groups agents and provides shared context (prompt) injected into every agent start.
  • Agent — a project-scoped, mutable definition. Agents belong to exactly one Project. prompt defines who the agent is and is directly editable (subject to RBAC).
  • Session — an ephemeral Kubernetes execution run, created exclusively via agent start. Only one active Session per Agent at a time.
  • Message — a single AG-UI event in the LLM conversation. Append-only; the canonical record of what happened in a session.
  • Inbox — a persistent message queue on an Agent. Messages survive across sessions and are drained into the start context at the next run.
  • Credential — a global secret. Stores a Personal Access Token or equivalent for an external provider (GitHub, GitLab, Jira, Google, Vertex AI, Kubeconfig). Consumed by runners at session start. Bound to Projects via RoleBindings — a single Credential can be shared across multiple Projects without duplication.
  • RoleBinding — binds a Role to a subject (user or project) at a given scope. Ownership and access for all Kinds is expressed through RoleBindings. The subject and scope are each represented as typed nullable FKs — exactly one FK is non-null, determined by scope.
  • Application — a GitOps binding that continuously syncs agent fleet definitions from a git repository to an Ambient instance. The Ambient equivalent of an Argo CD Application.

The stable address of an agent is {project_name}/{agent_name}. It holds the inbox and links to the active session.


Entity Relationship Diagram

%%{init: {'theme': 'default', 'themeVariables': {'attributeColor': '#111111', 'lineColor': '#ffffff', 'edgeLabelBackground': '#333333', 'fontFamily': 'monospace'}}}%%
erDiagram

    User {
        string ID PK
        string username
        string name
        string email
        jsonb  labels
        jsonb  annotations
        time   created_at
        time   updated_at
        time   deleted_at
    }

    Project {
        string ID PK "name-as-ID"
        string name
        string description
        string prompt "workspace-level context injected into every agent start"
        jsonb  labels
        jsonb  annotations
        string status
        time   created_at
        time   updated_at
        time   deleted_at
    }

    ProjectSettings {
        string ID PK
        string project_id FK
        string group_access
        string repositories
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── Agent (project-scoped, mutable) ──────────────────────────────────────

    Agent {
        string ID PK "KSUID"
        string project_id FK
        string parent_agent_id FK "nullable — parent agent for sub-agents"
        string owner_user_id FK "user who owns this agent"
        string name "human-readable; unique within project"
        string display_name "nullable — human-friendly display label"
        string description "nullable — purpose description"
        string prompt "who this agent is — mutable; access controlled via RBAC"
        string repo_url "nullable — primary repository for agent sessions"
        string workflow_id "nullable — default workflow for agent sessions"
        string llm_model "active LLM; default claude-sonnet-4-6"
        float  llm_temperature "default 0.7"
        int32  llm_max_tokens "default 4000"
        string bot_account_name "nullable — service account for git ops"
        string resource_overrides "nullable — JSON pod resource overrides"
        string environment_variables "nullable — JSON extra env vars"
        string current_session_id FK "nullable — denormalized for fast reads"
        jsonb  labels
        jsonb  annotations
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── Inbox (queue on Agent — messages waiting for next session) ────────────

    Inbox {
        string ID PK
        string agent_id FK "recipient — project/agent address"
        string from_agent_id FK "nullable — sender; null = human"
        string from_name "denormalized sender display name"
        text   body
        bool   read "false = unread; drained at session start"
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── Session (ephemeral run — created by user or via agent start) ─────────

    Session {
        string  ID PK
        string  name "human-readable display name"
        string  project_id FK "nullable — direct project context (no agent)"
        string  agent_id FK "nullable — set when started via agent ignite"
        string  created_by_user_id FK "who created or started the session"
        string  assigned_user_id FK "nullable — override for session ownership"
        string  parent_session_id FK "nullable — source session for clones"
        string  prompt "task scope for this run"
        string  repo_url "nullable — primary repo for the session"
        string  repos "JSON array of RepoEntry (additional attached repos)"
        string  workflow_id "nullable — JSON-encoded workflow config"
        string  llm_model "active LLM; default claude-sonnet-4-6"
        float   llm_temperature "default 0.7"
        int32   llm_max_tokens "default 4000"
        int32   timeout "nullable — max session duration in seconds"
        string  bot_account_name "nullable — service account for git ops"
        string  resource_overrides "nullable — JSON pod resource overrides"
        string  environment_variables "nullable — JSON extra env vars"
        string  labels "JSON map; queryable tags"
        string  annotations "JSON map; freeform metadata"
        string  phase
        time    start_time
        time    completion_time
        string  kube_cr_name "Kubernetes CR / pod name (set to session ID on create)"
        string  kube_cr_uid
        string  kube_namespace
        string  sdk_session_id
        int32   sdk_restart_count
        string  conditions
        string  reconciled_repos
        string  reconciled_workflow
        time    created_at
        time    updated_at
        time    deleted_at
    }

    %% ── SessionMessage (AG-UI event stream — real LLM turns) ─────────────────

    SessionMessage {
        string ID PK
        string session_id FK
        int    seq "monotonic within session"
        string event_type "user | assistant | tool_use | tool_result | system | error"
        string payload "message body or JSON-encoded event"
        time   created_at
    }

    %% ── RBAC ─────────────────────────────────────────────────────────────────

    Role {
        string ID PK
        string name
        string display_name
        string description
        jsonb  permissions
        bool   built_in
        time   created_at
        time   updated_at
        time   deleted_at
    }

    RoleBinding {
        string ID PK
        string role_id FK
        string scope         "global | project | agent | session | credential"
        string user_id FK    "nullable — set when scope identifies a user subject"
        string project_id FK "nullable — set when scope=project"
        string agent_id FK   "nullable — set when scope=agent"
        string session_id FK "nullable — set when scope=session"
        string credential_id FK "nullable — set when scope=credential"
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── Credential (global PAT/token store, bound via RoleBindings) ──────────

    Credential {
        string ID PK "KSUID"
        string name "human-readable; globally unique"
        string description
        string provider "github | gitlab | jira | google | vertex | kubeconfig"
        string token "write-only; stored encrypted"
        string url "nullable; service instance URL"
        string email "nullable; required for Jira"
        jsonb  labels
        jsonb  annotations
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── ScheduledSession (project-scoped recurring agent trigger) ──────────

    ScheduledSession {
        string ID PK "KSUID"
        string project_id FK
        string agent_id FK "nullable — which Agent to ignite on each trigger"
        string name "human-readable; unique within project"
        string description
        string schedule "cron expression"
        string timezone "IANA timezone; default UTC"
        bool   enabled "false = suspended; schedule not evaluated"
        string session_prompt "injected as Session.prompt on each trigger"
        int32  timeout "nullable — max session duration in seconds for triggered sessions"
        int32  inactivity_timeout "nullable — idle timeout in seconds"
        bool   stop_on_run_finished "nullable — stop session when run completes"
        string runner_type "nullable — override runner type for triggered sessions"
        time   last_run_at "nullable; wall-clock time of last trigger"
        time   next_run_at "nullable; computed from schedule + timezone"
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── Application (GitOps sync — Argo CD for Ambient) ──────────────

    Application {
        string ID PK "KSUID"
        string name "unique; human-readable"
        string source_repo_url "git repository URL"
        string source_target_revision "branch, tag, or commit SHA"
        string source_path "path within repo to kustomize overlay"
        string destination_ambient_url "nullable — target Ambient API URL; null = local"
        string destination_project "target project name; created if CreateProject=true"
        string credential_id FK "nullable — Credential for remote Ambient auth; required when destination_ambient_url is set"
        bool   auto_sync "enable automated sync on git change"
        bool   auto_prune "delete resources removed from git"
        bool   self_heal "re-sync when live state drifts"
        string sync_options "comma-separated: CreateProject=true, etc."
        int    retry_limit "max sync retries on failure"
        string sync_status "Synced | OutOfSync | Unknown"
        string health_status "Healthy | Degraded | Progressing | Unknown"
        string sync_revision "last successfully synced git commit SHA"
        string operation_phase "Succeeded | Failed | Running | idle"
        string operation_message "human-readable sync result summary"
        jsonb  resource_status "per-resource sync/health detail"
        jsonb  conditions "error conditions array"
        jsonb  labels
        jsonb  annotations
        time   last_synced_at "timestamp of last successful sync"
        time   created_at
        time   updated_at
        time   deleted_at
    }

    %% ── Relationships ────────────────────────────────────────────────────────

    Project         ||--o{ ProjectSettings  : "has"
    Project         ||--o{ Agent            : "owns"
    RoleBinding     }o--o| Credential       : "credential_id"
    Project         ||--o{ ScheduledSession : "owns"

    User            }o--o{ RoleBinding      : "user_id"
    Project         }o--o{ RoleBinding      : "project_id"

    RoleBinding     }o--o| Agent            : "agent_id"
    RoleBinding     }o--o| Session          : "session_id"

    Agent           ||--o{ Session          : "runs"
    Agent           ||--o| Session          : "current_session"
    Agent           ||--o{ Inbox            : "receives"
    Agent           ||--o{ ScheduledSession : "scheduled_by"

    Inbox           }o--o| Agent            : "sent_from"

    Application }o--o| Project        : "syncs_to"
    Application }o--o| Credential     : "credential_id"

    Session         ||--o{ SessionMessage   : "streams"

    Role            ||--o{ RoleBinding      : "granted_by"

Application — GitOps Continuous Sync

Application is the Ambient equivalent of an Argo CD Application. It binds a git repository source (containing kustomize-based agent fleet definitions) to a destination Ambient instance and project, then continuously reconciles the desired state from git against the live state in the platform.

Core Concepts (Argo CD Mapping)

Argo CD ConceptAmbient EquivalentDescription
ApplicationApplicationDeclarative binding of source → destination
Source (repo + path + revision)source_repo_url + source_path + source_target_revisionGit repo containing kustomize overlays of Projects, Agents, Credentials, RoleBindings
Application Source TypeAlways KustomizeThe CLI's built-in kustomize engine renders the manifests
Destination (cluster + namespace)destination_ambient_url + destination_projectTarget Ambient instance + project name
Target StateRendered kustomize outputThe desired set of Projects, Agents, Credentials, RoleBindings, and Inbox seeds from git
Live StateCurrent API server stateWhat actually exists in the destination Ambient's project
Sync Statussync_statusWhether live state matches target state: Synced, OutOfSync, Unknown
Sync Operation/sync sub-resourceThe act of applying target state to live state
Refresh/refresh sub-resourceFetch latest from git, render kustomize, diff against live state
Healthhealth_statusAre all synced agents healthy? Healthy, Degraded, Progressing, Unknown
Self-Healself_heal flagRe-sync when live state drifts (agent modified via UI, deleted manually)
Pruneauto_prune flagDelete agents/resources from Ambient that no longer exist in git

What Gets Synced

An Application syncs project-scoped fleet definitions — a subset of resource kinds that acpctl apply -k handles (excluding infrastructure inventory kinds like Cluster and Ambient):

KindSync Behavior
ProjectCreated if CreateProject=true in sync_options; patched (description, prompt, labels, annotations) on subsequent syncs
AgentCreated or patched within the destination project; prompt, labels, annotations updated
CredentialCreated if not present; idempotent by name
RoleBindingCreated if not present; idempotent by user+role+scope key. Escalation-bound: the sync engine can only create RoleBindings at or below the level of the service credential it uses (see Design Decisions).
Inbox (seed messages)Idempotent delivery — only new messages (by from_agent_id + body content hash dedup) are posted. Uses immutable from_agent_id FK, not mutable from_name.

What Does NOT Get Synced

KindWhy
SessionEphemeral run artifact. Created via agent start, not via GitOps.
SessionMessageAppend-only event stream.
ScheduledSessionProject-scoped trigger config; future sync candidate.
UserIdentity record.
RoleRBAC definition (platform-scoped, not project-scoped).

Field Reference

FieldNotes
nameUnique, human-readable. The stable address of this sync binding.
source_repo_urlGit repository URL. HTTPS or SSH.
source_target_revisionBranch name, tag, or commit SHA. Default: main.
source_pathRelative path within the repo to a kustomize directory (must contain kustomization.yaml).
credential_idNullable FK → Credential. The stored credential providing authentication for the destination Ambient's REST API. Required when destination_ambient_url is set. Uses the same write-only encrypted storage as all Credentials. The credential's token is resolved at sync time via GET /credentials/{cred_id}/token (gated by credential:token-reader). Null when targeting the local Ambient (controller uses its own service identity).
destination_ambient_urlNullable. The Ambient API server URL to sync to. Null = local Ambient (this API server). When set, credential_id must also be set — async polling controllers have no request context to forward a token from.
destination_projectTarget project name. The project is created on first sync if CreateProject=true is in sync_options.
auto_syncIf true, the controller polls the git repo and syncs automatically when changes are detected. If false, sync is manual via POST /sync.
auto_pruneIf true, resources in the live state that are absent from the target state are deleted. If false, orphaned resources are left in place. WARNING: Pruning a Project is permanently destructive. All Agents, Sessions, Inbox messages, and SessionMessages in the project are cascade-deleted. The sync engine will never auto-prune a Project — Project removal requires manual confirmation via POST /sync with explicit prune: true and prune_project: true flags. Agent-level pruning operates normally under auto_prune.
self_healIf true, the controller re-syncs when live state drifts from target state (e.g., an agent's prompt is changed via the UI). If false, drift is allowed.
sync_optionsComma-separated option flags. Initial options: CreateProject=true.
retry_limitMax number of automatic retries on sync failure. Default: 3.
sync_statusComputed on refresh. Synced = live matches target. OutOfSync = differences detected. Unknown = not yet refreshed.
health_statusComputed from synced resources. Healthy = all synced resources exist in the destination and match the target state (name, prompt, labels, annotations match git). Degraded = one or more synced resources are missing, have field drift from target state, or failed to apply. Progressing = sync operation is currently running. Unknown = not yet assessed (never refreshed). Health is assessed per-resource and aggregated — any single Degraded resource makes the whole application Degraded.
sync_revisionThe git commit SHA of the last successful sync.
operation_phaseState of the last sync operation: Succeeded, Failed, Running, or empty if never synced.
operation_messageHuman-readable summary, e.g. "3 created, 1 configured, 0 pruned".
resource_statusJSONB array of per-resource sync results: [{"kind": "Agent", "name": "lead", "status": "Synced", "health": "Healthy", "message": "configured"}].
conditionsJSONB array of error conditions: [{"type": "SyncError", "message": "...", "lastTransitionTime": "..."}].
last_synced_atTimestamp of the last successful sync completion.

Sync Lifecycle

1. Refresh: clone/fetch repo at source_target_revision
2. Render:  build kustomize at source_path → flat manifest stream
3. Diff:    compare rendered manifests against live state in destination project
4. Sync:    apply creates/patches/deletes to reconcile live → target
5. Status:  update sync_status, health_status, resource_status, operation_*

For automated sync (auto_sync=true), this lifecycle runs on a configurable polling interval (default: 3 minutes). For manual sync, it runs on POST /api/ambient/v1/applications/{id}/sync.

Destination Resolution

Application.destination_ambient_url set?
  |── null  ──> local Ambient (this API server's own service layer)
  |            ──> controller uses its own service identity
  |── set   ──> remote Ambient (SDK client pointed at the URL)
              ──> credential_id MUST be set (FK → Credential)
              ──> token resolved at sync time via GET /credentials/{id}/token

When targeting a remote Ambient, the sync engine acts as an API client to the remote Ambient's REST API, authenticated via the stored Credential. The credential is resolved at sync time — the controller never caches tokens beyond a single sync cycle. This is different from how Sessions use kubeconfig for direct K8s provisioning — the Application works entirely at the Ambient API layer.

Unsupported Kinds in Sync

The kustomize rendering engine (acpctl apply -k) supports additional resource kinds beyond what Application syncs (e.g., Cluster, Ambient — infrastructure inventory kinds). When a rendered kustomize tree contains documents of unsupported kinds, the sync engine silently skips them. Each skipped document is recorded in resource_status with a Skipped status:

{"kind": "Ambient", "name": "staging-cluster", "status": "Skipped", "health": "Unknown", "message": "infrastructure inventory — not synced by Application"}

This is not an error. The sync operation proceeds with the supported kinds and reports operation_phase: Succeeded if all syncable resources apply cleanly.

Multi-Environment Promotion

Promotion across environments is expressed as multiple Applications, each pointing to a different overlay and destination:

# Dev — auto-sync from main, auto-prune
kind: Application
name: my-fleet-dev
source:
  repo_url: https://gitlab.cee.redhat.com/ambient-code/ambient-code-gitops.git
  target_revision: main
  path: ambient/overlays/dev
destination:
  ambient_url: null   # local
  project: my-fleet
auto_sync: true
auto_prune: true
self_heal: true

---
# Staging — manual sync from release branch, no prune
kind: Application
name: my-fleet-staging
source:
  repo_url: https://gitlab.cee.redhat.com/ambient-code/ambient-code-gitops.git
  target_revision: release/v1.2
  path: ambient/overlays/staging
destination:
  ambient_url: https://ambient-staging.apps.example.com
  credential: staging-ambient-pat   # Credential name; resolved to credential_id
  project: my-fleet
auto_sync: false
auto_prune: false
self_heal: false

Promotion is a git operation: merge the dev overlay changes into the release branch, then sync the staging Application.


Agent — Project-Scoped Mutable Definition

Agent is scoped to a Project. The stable address is {project_name}/{agent_name}.

FieldNotes
nameHuman-readable, unique within the project. Used as display name and in addressing.
display_nameNullable. Human-friendly label for UI display; does not affect addressing.
descriptionNullable. Free-text purpose description.
promptDefines who the agent is. Mutable via PATCH. Access controlled by RBAC (agent:editor or higher).
parent_agent_idNullable FK. Set when this agent was spawned as a sub-agent by another agent.
owner_user_idFK to the User who owns this agent. Set at creation; matches the authenticated caller.
repo_urlNullable. Primary repository URL cloned into every session the agent starts. Copied to Session.repo_url on ignite.
workflow_idNullable. Default workflow identifier injected into sessions. Copied to Session.workflow_id on ignite.
llm_modelActive LLM model name. Default: claude-sonnet-4-6. Copied to Session.llm_model on ignite.
llm_temperatureLLM sampling temperature. Default: 0.7. Copied to Session.llm_temperature on ignite.
llm_max_tokensMax tokens per LLM response. int32, default: 4000. Copied to Session.llm_max_tokens on ignite.
bot_account_nameNullable. Service account name for git operations inside sessions. Copied to Session.bot_account_name on ignite.
resource_overridesNullable. JSON-encoded pod resource requests/limits override for sessions spawned by this agent. Copied to Session.resource_overrides on ignite.
environment_variablesNullable. JSON-encoded extra environment variables injected into session pods. Copied to Session.environment_variables on ignite.
current_session_idDenormalized FK to the active Session. Null when no session is running. Used by Project Home for fast reads.

Agent is mutable. PATCH updates in place. There is no versioning. If you need to track prompt history, use labels/annotations or an external audit log.

Field propagation on ignite: When POST /agents/{id}/start creates a new Session, the ignite_handler copies repo_url, workflow_id, llm_model, llm_temperature, llm_max_tokens, bot_account_name, resource_overrides, and environment_variables from the Agent to the new Session. Fields set directly in the start request body override these defaults.

POST /projects/{id}/agents          → create agent in this project
PATCH /projects/{id}/agents/{id}    → update agent (name, prompt, labels, annotations)
GET /projects/{id}/agents/{id}      → read agent
DELETE /projects/{id}/agents/{id}   → soft delete

Only one active Session per Agent at a time. Start is idempotent — if an active session exists, start returns it. If not, a new session is created.


Inbox — Persistent Message Queue

Inbox messages are addressed to an Agent (agent_id). They are distinct from Session Messages:

InboxSessionMessage
ScopeAgent (persists across sessions)Session (ephemeral)
Created byHuman or another AgentLLM turn / runner gRPC push
DrainedAt session startNever — append-only stream
PurposeQueued intent waiting for next runReal LLM event stream

At session start, all unread Inbox messages are drained: marked read=true and injected as context into the Session prompt before the first SessionMessage turn.


Session — Ephemeral Run

Sessions are not directly creatable. They are run artifacts created exclusively via POST /projects/{project_id}/agents/{agent_id}/start.

Session.prompt scopes the task for this specific run — separate from Agent.prompt which defines who the agent is.

Project.prompt  → "This workspace builds the Ambient platform API server in Go."
Agent.prompt    → "You are a backend engineer specializing in Go APIs..."
Inbox messages  → "Please also review the RBAC middleware while you're in there"
Session.prompt  → "Implement the session messages handler. Repo: github.com/..."

All four are assembled into the start context in that order. Pokes roll downhill.


SessionMessage — AG-UI Event Stream

SessionMessages are the real LLM conversation. They are appended by the runner via gRPC PushSessionMessage and streamed to clients via SSE.

seq is monotonically increasing within a session. event_type follows the AG-UI protocol: user, assistant, tool_use, tool_result, system, error.

SessionMessages are never deleted or edited. They are the canonical record of what happened in a session.

Two Event Streams

EndpointSourcePersistencePurpose
GET /sessions/{id}/messagesAPI server gRPC fan-outPersisted in DB (replay from seq=0)Durable stream; supports replay and history
GET /sessions/{id}/eventsRunner pod SSE (GET /events/{thread_id})Ephemeral; runner-local in-memory queueLive AG-UI turn events during an active run

The runner's /events/{thread_id} endpoint registers an asyncio queue into bridge._active_streams[thread_id] and streams every AG-UI event as SSE until RUN_FINISHED / RUN_ERROR or client disconnect. The API server's /sessions/{id}/events proxies this from the runner pod for the active session, routing via pod IP or session service. Keepalive pings fire every 30s to hold the connection open.


ScheduledSession — Recurring Agent Trigger

A ScheduledSession is a project-scoped definition that ignites an Agent on a recurring cron schedule. Each trigger creates a new Session with session_prompt injected as the task scope for that run.

FieldNotes
nameHuman-readable, unique within the project.
agent_idWhich Agent to ignite. Must exist in the same project.
scheduleStandard cron expression (e.g. "0 9 * * 1-5" = 9 AM on weekdays).
timezoneIANA timezone string (e.g. "America/New_York"). Defaults to UTC.
enabledfalse suspends evaluation without deleting the schedule.
session_promptInjected as Session.prompt on each trigger — the recurring task.
last_run_atWall-clock time of the last trigger. Null if never triggered.
next_run_atComputed from schedule + timezone. Updated after each trigger.

Trigger semantics: Each trigger calls POST /projects/{id}/agents/{agent_id}/start, which is idempotent. If the Agent already has an active Session at trigger time, the trigger is skipped and recorded as a missed run in the runs list.

Manual trigger: POST .../trigger ignites the Agent immediately outside the cron schedule, using the same session_prompt. Useful for testing or one-off runs.

Suspend / Resume: POST .../suspend sets enabled=false; POST .../resume sets enabled=true. These are named convenience actions equivalent to PATCH {enabled: false|true}.


CLI Reference (acpctl)

The acpctl CLI mirrors the API 1-for-1. Every REST operation has a corresponding command.

API ↔ CLI Mapping

Projects

REST APIacpctl CommandStatus
GET /projectsacpctl get projects✅ implemented
GET /projects/{id}acpctl get project <name>✅ implemented
POST /projectsacpctl create project --name <n> [--description <d>]✅ implemented
PATCH /projects/{id}acpctl project update [--name <n>] [--description <d>] [--prompt <p>]✅ implemented
DELETE /projects/{id}acpctl delete project <name>✅ implemented
(context switch)acpctl project <name>✅ implemented
(context view)acpctl project current✅ implemented

Agents (Project-Scoped)

REST APIacpctl CommandStatus
GET /projects/{id}/agentsacpctl agent list --project-id <p>✅ implemented
GET /projects/{id}/agents/{agent_id}acpctl agent get --project-id <p> --agent-id <id>✅ implemented
POST /projects/{id}/agentsacpctl agent create --project-id <p> --name <n> [--prompt <p>]✅ implemented
PATCH /projects/{id}/agents/{agent_id}acpctl agent update --project-id <p> --agent-id <id> [--name <n>] [--prompt <p>]✅ implemented
DELETE /projects/{id}/agents/{agent_id}acpctl agent delete --project-id <p> --agent-id <id> --confirm✅ implemented
POST /projects/{id}/agents/{agent_id}/startacpctl start <agent-id> --project-id <p> [--prompt <t>]✅ implemented
GET /projects/{id}/agents/{agent_id}/startacpctl agent start-preview --project-id <p> --agent-id <id>✅ implemented
GET /projects/{id}/agents/{agent_id}/sessionsacpctl agent sessions --project-id <p> --agent-id <id>✅ implemented
GET /projects/{id}/agents/{agent_id}/inboxacpctl inbox list --project-id <p> --pa-id <id>✅ implemented
POST /projects/{id}/agents/{agent_id}/inboxacpctl inbox send --project-id <p> --pa-id <id> --body <text>✅ implemented
PATCH /projects/{id}/agents/{agent_id}/inbox/{msg_id}acpctl inbox mark-read --project-id <p> --pa-id <id> --msg-id <id>✅ implemented
DELETE /projects/{id}/agents/{agent_id}/inbox/{msg_id}acpctl inbox delete --project-id <p> --pa-id <id> --msg-id <id>✅ implemented

Sessions

REST APIacpctl CommandStatus
GET /sessionsacpctl get sessions✅ implemented
GET /sessionsacpctl get sessions -w✅ implemented (gRPC watch)
GET /sessions/{id}acpctl get session <id>✅ implemented
GET /sessions/{id}acpctl describe session <id>✅ implemented
DELETE /sessions/{id}acpctl delete session <id>✅ implemented
GET /sessions/{id}/messagesacpctl session messages <id>✅ implemented
POST /sessions/{id}/messagesacpctl session send <id> <message>✅ implemented
POST /sessions/{id}/messages + GET /sessions/{id}/eventsacpctl session send <id> <message> -f✅ implemented
POST /sessions/{id}/messages + GET /sessions/{id}/eventsacpctl session send <id> <message> -f --json✅ implemented
GET /sessions/{id}/eventsacpctl session events <id>✅ implemented

ScheduledSessions (Project-Scoped)

REST APIacpctl CommandStatus
GET /projects/{id}/scheduled-sessionsacpctl scheduled-session list✅ implemented
GET /projects/{id}/scheduled-sessions/{sched_id}acpctl scheduled-session get <name>✅ implemented
POST /projects/{id}/scheduled-sessionsacpctl scheduled-session create --name <n> --agent-id <a> --schedule <cron> [--prompt <p>] [--timezone <tz>]✅ implemented
PATCH /projects/{id}/scheduled-sessions/{sched_id}acpctl scheduled-session update <name> [--schedule <cron>] [--prompt <p>] [--enabled=false]✅ implemented
DELETE /projects/{id}/scheduled-sessions/{sched_id}acpctl scheduled-session delete <name> --confirm✅ implemented
POST .../suspendacpctl scheduled-session suspend <name>✅ implemented
POST .../resumeacpctl scheduled-session resume <name>✅ implemented
POST .../triggeracpctl scheduled-session trigger <name>✅ implemented
GET .../runsacpctl scheduled-session runs <name>✅ implemented

Session Operations

REST APIacpctl CommandStatus
GET /sessions/{id}/workspaceacpctl session workspace list <id>🔲 planned
GET /sessions/{id}/workspace/*pathacpctl session workspace get <id> <path>🔲 planned
PUT /sessions/{id}/workspace/*pathacpctl session workspace put <id> <path> [--file <f>]🔲 planned
DELETE /sessions/{id}/workspace/*pathacpctl session workspace delete <id> <path>🔲 planned
GET /sessions/{id}/filesacpctl session files list <id>🔲 planned
PUT /sessions/{id}/files/*pathacpctl session files upload <id> <path> [--file <f>]🔲 planned
DELETE /sessions/{id}/files/*pathacpctl session files delete <id> <path>🔲 planned
GET /sessions/{id}/git/statusacpctl session git status <id>🔲 planned
POST /sessions/{id}/git/configure-remoteacpctl session git configure-remote <id>🔲 planned
GET /sessions/{id}/git/branchesacpctl session git branches <id>🔲 planned
GET /sessions/{id}/repos/statusacpctl session repos list <id>🔲 planned
POST /sessions/{id}/reposacpctl session repos add <id> --repo <url>🔲 planned
DELETE /sessions/{id}/repos/{name}acpctl session repos remove <id> <repo>🔲 planned
POST /sessions/{id}/cloneacpctl session clone <id> [--name <n>]🔲 planned
POST /sessions/{id}/modelacpctl session model <id> --model <m>🔲 planned
GET /sessions/{id}/exportacpctl session export <id>🔲 planned
GET /sessions/{id}/pod-eventsacpctl session pod-events <id>🔲 planned
GET /sessions/{id}/tasksacpctl session tasks <id>🔲 planned
POST /sessions/{id}/tasks/{task_id}/stopacpctl session tasks stop <id> <task-id>🔲 planned
GET /sessions/{id}/tasks/{task_id}/outputacpctl session tasks output <id> <task-id>🔲 planned

Applications (GitOps)

REST APIacpctl CommandStatus
GET /applicationsacpctl get applications🔲 planned
GET /applications/{id}acpctl get application <name>🔲 planned
POST /applicationsacpctl create application --name <n> --repo <url> --path <p> [--revision <r>] [--project <p>] [--ambient-url <u>]🔲 planned
PATCH /applications/{id}acpctl update application <name> [--repo <url>] [--path <p>] [--auto-sync] [--auto-prune] [--self-heal]🔲 planned
DELETE /applications/{id}acpctl delete application <name> --confirm🔲 planned
POST /applications/{id}/syncacpctl sync application <name> [--prune] [--revision <r>]🔲 planned
POST /applications/{id}/refreshacpctl refresh application <name>🔲 planned
GET /applications/{id}/statusacpctl get application <name> -o wide🔲 planned

Credentials (Global)

REST APIacpctl CommandStatus
GET /credentialsacpctl credential list [--provider <p>]✅ implemented
POST /credentialsacpctl credential create --name <n> --provider <p> --token <t|@-> [--url <u>] [--email <e>] [--description <d>]✅ implemented
GET /credentials/{cred_id}acpctl credential get <id>✅ implemented
PATCH /credentials/{cred_id}acpctl credential update <id> [--token <t>] [--description <d>]✅ implemented
DELETE /credentials/{cred_id}acpctl credential delete <id> --confirm✅ implemented
GET /credentials/{cred_id}/tokenacpctl credential token <id>✅ implemented
POST /role_bindingsacpctl credential bind <cred-name> --project <project>✅ implemented

RBAC

REST APIacpctl CommandStatus
GET /rolesacpctl get roles✅ implemented
GET /roles/{id}acpctl get roles <id>✅ implemented
POST /rolesacpctl create role --name <n> [--permissions <json>]✅ implemented
DELETE /roles/{id}acpctl delete role <id>✅ implemented
GET /role_bindingsacpctl get role-bindings✅ implemented
GET /role_bindings/{id}acpctl get role-bindings <id>✅ implemented
POST /role_bindingsacpctl create role-binding --role-id <r> --scope <s> [--user-id <u>] [--project-id <p>] [--agent-id <a>] [--session-id <s>] [--credential-id <c>]✅ implemented
DELETE /role_bindings/{id}acpctl delete role-binding <id>✅ implemented

Auth & Context

Operationacpctl CommandStatus
Authenticateacpctl login [SERVER_URL] --token <t>✅ implemented
Log outacpctl logout✅ implemented
Identityacpctl whoami✅ implemented
Config getacpctl config get <key>✅ implemented
Config setacpctl config set <key> <value>✅ implemented

acpctl apply — Declarative Fleet Management

acpctl apply reconciles Projects and Agents from declarative YAML files, mirroring kubectl apply semantics. It is the primary way to provision and update entire agent fleets from the .ambient/teams/ directory tree.

Supported Kinds

KindFields applied
Projectname, description, prompt, labels, annotations
Agentname, prompt, labels, annotations, inbox (seed messages)
Credentialname, description, provider, token (env var reference), url, email, labels, annotations — global resource; use credential bind to grant project access

Agent resources in .ambient/teams/ files also carry an inbox list of seed messages. On apply, any message in the list is posted to the agent's inbox if an identical message (same from_name + body) does not already exist there.

-f — File or Directory

acpctl apply -f <file>               # apply a single YAML file
acpctl apply -f <dir>                # apply all *.yaml files in the directory (non-recursive)
acpctl apply -f -                    # read from stdin

Each file may contain one or more YAML documents separated by ---. Documents with unrecognised kind values are skipped with a warning.

Apply behaviour per resource:

  • Project: if a project with name already exists, PATCH it (description, prompt, labels, annotations). If it does not exist, POST to create it.
  • Agent: resolved within the current project context. If an agent with name already exists in the project, PATCH it (prompt, labels, annotations). If it does not exist, POST to create it. After upsert, post any inbox seed messages not already present.

Output (default — one line per resource):

project/ambient-platform configured
agent/lead configured
agent/api created
agent/fe created

With -o json: JSON array of all applied resources.

-k — Kustomize Directory

acpctl apply -k <dir>                # build kustomization in <dir> and apply the result

Equivalent to: build the kustomization (resolve bases, resources, merge patches) into a flat manifest stream, then apply each document in order.

The kustomization schema is a subset of Kubernetes Kustomize, restricted to the fields meaningful for Ambient resources:

kind: Kustomization

resources:           # relative paths to YAML files included in this build
  - project.yaml
  - lead.yaml

bases:               # other kustomization directories to include first
  - ../../base

patches:             # strategic-merge patches applied after resource collection
  - path: project-patch.yaml
    target:
      kind: Project
      name: ambient-platform
  - path: agents-patch.yaml
    target:
      kind: Agent   # no name = apply to all Agent resources

Patches use strategic merge: scalar fields overwrite, maps merge, sequences replace.

Output is identical to -f.

Examples

# Apply the full base fleet
acpctl apply -f .ambient/teams/base/

# Apply the dev overlay (resolves base + patches)
acpctl apply -k .ambient/teams/overlays/dev/

# Apply a single agent file
acpctl apply -f .ambient/teams/base/lead.yaml

# Dry-run: show what would change without applying
acpctl apply -k .ambient/teams/overlays/prod/ --dry-run

# Pipe from stdin
cat lead.yaml | acpctl apply -f -

Flags

FlagDescription
-f <path>File, directory, or - for stdin. Mutually exclusive with -k.
-k <dir>Kustomize directory. Mutually exclusive with -f.
--dry-runPrint what would be applied without making API calls.
-o jsonJSON output (array of applied resources).
--project <name>Override project context for Agent resources.

Status column

OutputMeaning
createdResource did not exist; POST succeeded.
configuredResource existed; PATCH applied one or more changes.
unchangedResource existed and matched desired state; no API call made.

CLI reference row additions

CommandStatus
acpctl apply -f <path>✅ implemented
acpctl apply -k <dir>✅ implemented

Global Flags

FlagDescription
--insecure-skip-tls-verifySkip TLS certificate verification
-o jsonJSON output (most get/create commands)
-o wideWide table output
--limit <n>Max items to return (default: 100)
-w / --watchLive watch mode (sessions only)
--watch-timeout <duration>Watch timeout (default: 30m)

Project Context

The CLI maintains a current project context in ~/.acpctl/config.yaml (also overridable via AMBIENT_PROJECT env var). Most operations that require project_id read it from context automatically.

acpctl login https://api.example.com --token $TOKEN
acpctl project my-project
acpctl get sessions
acpctl create agent --name overlord --prompt "You coordinate the fleet..."
acpctl start overlord

API Reference

Projects

GET    /api/ambient/v1/projects                              list projects
POST   /api/ambient/v1/projects                              create project
GET    /api/ambient/v1/projects/{id}                         read project
PATCH  /api/ambient/v1/projects/{id}                         update project
DELETE /api/ambient/v1/projects/{id}                         delete project

GET    /api/ambient/v1/projects/{id}/role_bindings           RBAC bindings scoped to this project

Agents (Project-Scoped)

GET    /api/ambient/v1/projects/{id}/agents                  list agents in this project
POST   /api/ambient/v1/projects/{id}/agents                  create agent
GET    /api/ambient/v1/projects/{id}/agents/{agent_id}       read agent
PATCH  /api/ambient/v1/projects/{id}/agents/{agent_id}       update agent (name, prompt, labels, annotations)
DELETE /api/ambient/v1/projects/{id}/agents/{agent_id}       soft delete

POST   /api/ambient/v1/projects/{id}/agents/{agent_id}/start     start — creates Session (idempotent; one active at a time)
GET    /api/ambient/v1/projects/{id}/agents/{agent_id}/start     preview start context (dry run — no session created)
GET    /api/ambient/v1/projects/{id}/agents/{agent_id}/sessions  session run history
GET    /api/ambient/v1/projects/{id}/agents/{agent_id}/inbox     read inbox (unread first)
POST   /api/ambient/v1/projects/{id}/agents/{agent_id}/inbox     send message to this agent's inbox
PATCH  /api/ambient/v1/projects/{id}/agents/{agent_id}/inbox/{msg_id}   mark message read
DELETE /api/ambient/v1/projects/{id}/agents/{agent_id}/inbox/{msg_id}   delete message

GET    /api/ambient/v1/projects/{id}/agents/{agent_id}/role_bindings    RBAC bindings

Ignite Response

POST /projects/{id}/agents/{agent_id}/start is idempotent:

  • If a session is already active, it is returned as-is.
  • If no active session exists, a new one is created.
  • Unread Inbox messages are drained (marked read) and injected into the start context.
{
  "session": {
    "id": "2abc...",
    "agent_id": "1def...",
    "phase": "pending",
    "created_by_user_id": "...",
    "created_at": "2026-03-20T00:00:00Z"
  },
  "start_context": "# Agent: API\n\nYou are API...\n\n## Inbox\n...\n\n## Task\n..."
}

The start context assembles in order:

  1. Project.prompt (workspace context — shared by all agents in this project)
  2. Agent.prompt (who you are)
  3. Drained Inbox messages (what others have asked you to do)
  4. Session.prompt (what this run is focused on)
  5. Peer Agent roster with latest status

Sessions

Sessions are not directly creatable.

GET    /api/ambient/v1/sessions                                              list sessions
GET    /api/ambient/v1/sessions/{id}                                         read session
DELETE /api/ambient/v1/sessions/{id}                                         cancel or delete session

GET    /api/ambient/v1/sessions/{id}/messages                                list messages (history)
POST   /api/ambient/v1/sessions/{id}/messages                                push a message (human turn)
GET    /api/ambient/v1/sessions/{id}/events                                  SSE live event stream from runner pod
GET    /api/ambient/v1/sessions/{id}/role_bindings                           RBAC bindings

Session Messages (Top-Level)

GET    /api/ambient/v1/session_messages                                      list messages across sessions (SDK access)

Top-level endpoint for SDK and internal consumers (e.g. the control plane). Supports TSL search, ordering, and size limit via query parameters:

ParameterExamplePurpose
searchsession_id='01HABC...'TSL filter; must contain session_id = '...'
orderByseq descColumn + direction (seq asc, seq desc, created_at desc)
size1Max rows returned

Example — fetch the latest message for a session:

GET /api/ambient/v1/session_messages?search=session_id='01HABC...'&orderBy=seq desc&size=1

Response shape:

{
  "kind": "SessionMessageList",
  "page": 1,
  "size": 1,
  "total": 1,
  "items": [{ "id": "...", "session_id": "...", "seq": 42, "event_type": "assistant", "payload": "...", "created_at": "..." }]
}

Used by the control plane at session restart to resolve the maximum seq for RESUME_AFTER_SEQ.

Applications (GitOps)

GET    /api/ambient/v1/applications                  list all applications
POST   /api/ambient/v1/applications                  create application
GET    /api/ambient/v1/applications/{id}              read application (includes status)
PATCH  /api/ambient/v1/applications/{id}              update application
DELETE /api/ambient/v1/applications/{id}              delete application

POST   /api/ambient/v1/applications/{id}/sync         trigger sync (apply target state to live state)
POST   /api/ambient/v1/applications/{id}/refresh      refresh (fetch git, diff against live, update sync_status)
GET    /api/ambient/v1/applications/{id}/status       read sync/health status and per-resource detail

Sync Request

POST /applications/{id}/sync accepts an optional body:

{
  "prune": true,
  "revision": "abc123"
}

prune overrides the application-level auto_prune for this sync only. revision overrides source_target_revision for a one-time sync at a specific commit.

Status Response

GET /applications/{id}/status returns the sync and health detail:

{
  "sync_status": "Synced",
  "health_status": "Healthy",
  "sync_revision": "abc123def456",
  "last_synced_at": "2026-06-03T12:05:00Z",
  "operation_phase": "Succeeded",
  "operation_message": "3 created, 1 configured, 0 pruned",
  "resource_status": [
    {"kind": "Project", "name": "my-fleet", "status": "Synced", "health": "Healthy", "message": "created"},
    {"kind": "Agent", "name": "lead", "status": "Synced", "health": "Healthy", "message": "configured"},
    {"kind": "Agent", "name": "engineer", "status": "Synced", "health": "Healthy", "message": "unchanged"}
  ],
  "conditions": []
}

Workspace Files

Read and write files in a running session's workspace. Session must be in Running phase.

GET    /api/ambient/v1/sessions/{id}/workspace                               list workspace files
GET    /api/ambient/v1/sessions/{id}/workspace/*path                         read file content
PUT    /api/ambient/v1/sessions/{id}/workspace/*path                         write file content
DELETE /api/ambient/v1/sessions/{id}/workspace/*path                         delete file

Pre-Upload Files

Stage files into S3 before the session pod starts. Files are hydrated into the workspace at start time. Max 10 MB per file.

GET    /api/ambient/v1/sessions/{id}/files                                   list staged files
PUT    /api/ambient/v1/sessions/{id}/files/*path                             stage a file
DELETE /api/ambient/v1/sessions/{id}/files/*path                             remove staged file

Git

GET    /api/ambient/v1/sessions/{id}/git/status                              git status in session workspace
POST   /api/ambient/v1/sessions/{id}/git/configure-remote                    configure git remote
GET    /api/ambient/v1/sessions/{id}/git/branches                            list branches

Repos

Attach additional repositories to a session workspace.

GET    /api/ambient/v1/sessions/{id}/repos/status                            list attached repos and clone status
POST   /api/ambient/v1/sessions/{id}/repos                                   attach an additional repo
DELETE /api/ambient/v1/sessions/{id}/repos/{repo_name}                       detach a repo

Operational

POST   /api/ambient/v1/sessions/{id}/clone                                   clone session (new session from same config)
PATCH  /api/ambient/v1/sessions/{id}/displayname                             update display name
POST   /api/ambient/v1/sessions/{id}/model                                   switch active model
GET    /api/ambient/v1/sessions/{id}/workflow/metadata                       get active workflow and metadata
POST   /api/ambient/v1/sessions/{id}/workflow                                select workflow
GET    /api/ambient/v1/sessions/{id}/pod-events                              Kubernetes pod events for this session
GET    /api/ambient/v1/sessions/{id}/oauth/{provider}/url                    get OAuth redirect URL for provider
GET    /api/ambient/v1/sessions/{id}/export                                  export session transcript

Runner Protocol

These endpoints proxy directly to the runner pod. Session must be in Running phase. Returns 502 if the runner is unreachable.

POST   /api/ambient/v1/sessions/{id}/interrupt                               interrupt the active run
POST   /api/ambient/v1/sessions/{id}/feedback                                submit feedback event (Langfuse)
GET    /api/ambient/v1/sessions/{id}/capabilities                            runner framework and capabilities
GET    /api/ambient/v1/sessions/{id}/mcp/status                              MCP server instance status
GET    /api/ambient/v1/sessions/{id}/tasks                                   list background tasks
GET    /api/ambient/v1/sessions/{id}/tasks/{task_id}/output                  get task output (max 10 MB)
POST   /api/ambient/v1/sessions/{id}/tasks/{task_id}/stop                    stop background task

Credentials (Global)

Credentials are global resources. Access to credentials is granted via RoleBindings — bind a credential to a Project, Agent, or Session scope to make it available to runners in that scope.

Designed paths (global — pending implementation):

GET    /api/ambient/v1/credentials                                        list credentials (filtered by caller's RoleBindings)
GET    /api/ambient/v1/credentials?provider={provider}                    filter by provider
POST   /api/ambient/v1/credentials                                        create a credential
GET    /api/ambient/v1/credentials/{cred_id}                              read credential (metadata only; token never returned)
PATCH  /api/ambient/v1/credentials/{cred_id}                              update credential
DELETE /api/ambient/v1/credentials/{cred_id}                              soft delete
GET    /api/ambient/v1/credentials/{cred_id}/token                        fetch raw token — restricted to credential:token-reader

Note: credential bind (via POST /role_bindings with scope=credential, credential_id, and project_id) is planned but not yet implemented.

token is accepted on POST and PATCH but never returned by standard read endpoints. GET .../token is gated by credential:token-reader. See Security Spec — Token Reader Role Grant for runtime authorization semantics.

Provider Enum

ProviderServiceToken typeurlemail
githubGitHub.com or GitHub EnterprisePersonal Access Tokenoptional; required for GHE
gitlabGitLab.com or self-hostedPersonal Access Tokenoptional; required for self-hosted
jiraJira Cloud (Atlassian)API Tokenrequired (Atlassian instance URL)required (used in Basic auth)
googleGoogle Cloud / WorkspaceService Account JSON serialized to string
vertexVertex AI (GCP)GCP service account key
kubeconfigKubernetes clustersKubeconfig file serialized to string

Token Response Shape (Runner)

When a runner fetches a credential, the response payload shape is consistent across providers:

{ "provider": "gitlab", "token": "glpat-...",       "url": "https://gitlab.myco.com" }
{ "provider": "github", "token": "github_pat_...",  "url": "https://github.com" }
{ "provider": "jira",   "token": "ATATT3x...",      "url": "https://myco.atlassian.net", "email": "bot@myco.com" }
{ "provider": "google", "token": "{\"type\":\"service_account\", ...}" }

token is always present. url and email are included when set. Google's token field carries the full Service Account JSON serialized as a string.


RBAC

RoleBinding — Nullable FK Design

RoleBinding is a typed nullable FK table. Each row has exactly one non-null FK, determined by scope. There is no polymorphic scope_id string — every FK points to a real table with referential integrity.

scope valueNon-null FKMeaning
global(none)Role applies across the entire platform
projectproject_idRole applies within a specific project
agentagent_idRole applies to a specific agent
sessionsession_idRole applies to a specific session run
credentialcredential_idRole governs access to a specific credential

user_id is a separate, independently nullable FK — it identifies the user who holds the binding when the grant is user-specific. It is null when the grant is project-level (not tied to a specific human):

Use caseuser_idscope FKMeaning
User A owns Credential Yuser_id=Acredential_id=YA can CRUD credential Y
Credential Y bound to Project Xuser_id=NULLcredential_id=Y + project_id=XProject X can access credential Y
User A is project:owner of Project Xuser_id=Aproject_id=XA owns project X
Global platform:admin grantuser_id=A(none)A has platform-wide admin

For credential→project bindings, both credential_id and project_id are non-null. This is the one exception to the "single FK per row" pattern — a credential binding names both the credential (the resource) and the project (the recipient). user_id is null because the grant is not user-specific; it applies to the entire project.

Scopes

ScopeFK setMeaning
global(none)Applies across the entire platform
projectproject_idApplies to all resources in a specific project
agentagent_idApplies to a specific Agent and all its sessions
sessionsession_idApplies to one session run only
credentialcredential_idGoverns access to a specific Credential

Effective permissions = union of all applicable bindings (global ∪ project ∪ agent ∪ session). No deny rules.

Credential Access — Global with RoleBinding Grants

Credentials are global resources. A credential is made accessible to a Project by creating a RoleBinding with scope=credential, credential_id=<cred>, project_id=<project>, and user_id=NULL. At session start, the resolver finds all scope=credential bindings where project_id matches the session's project and returns the matching credentials.

A single Credential can be shared across multiple Projects by creating one binding per project — no duplication of the Credential record.

See Security Spec — Credential Access via RoleBindings for runtime authorization semantics.

Built-in Roles

RoleDescription
platform:adminFull access to everything
platform:viewerRead-only across the platform
project:ownerFull control of a project and all its agents
project:editorCreate/update Agents, ignite, send messages
project:viewerRead-only within a project
agent:operatorIgnite and message a specific Agent
agent:editorUpdate prompt and metadata on a specific Agent
agent:observerRead a specific Agent and its sessions
agent:runnerMinimum viable pod credential: read agent, push messages, send inbox
credential:ownerFull CRUD on credentials the user created. Bind credentials to projects the user has project:owner on.
credential:viewerRead metadata (not token) on credentials bound to projects the user has access to.
credential:token-readerFetch the raw token via GET /credentials/{cred_id}/token. Granted only to runner service accounts at session start. Human users do not hold this role.
gitops:adminFull CRUD on Applications; trigger sync/refresh. Platform-scoped — grantable only by platform:admin.
gitops:viewerRead-only on Applications and their status. Platform-scoped — grantable only by platform:admin.

Permission Matrix

RoleProjectsAgentsSessionsInboxCredentialsAppsHomeRBAC
platform:adminfullfullfullfullfullfullfullfull
platform:viewerread/listread/listread/listread/listread/listreadread/list
project:ownerfullfullfullfullmanage bindingslocal-only (own project)readproject+agent bindings
project:editorreadcreate/update/igniteread/listsend/readread
project:viewerreadread/listread/listread
gitops:adminfull (any destination)
gitops:viewerread/list
agent:operatorupdate/igniteread/listsend/read
agent:editorupdate
agent:observerreadread/list
agent:runnerreadreadsend
credential:ownercreate/update/delete + bind
credential:viewerread/list (metadata only)
credential:token-readertoken: read

RBAC Endpoints

GET    /api/ambient/v1/roles                                              ✅ implemented
GET    /api/ambient/v1/roles/{id}                                         ✅ implemented
POST   /api/ambient/v1/roles                                              ✅ implemented
PATCH  /api/ambient/v1/roles/{id}                                         ✅ implemented
DELETE /api/ambient/v1/roles/{id}                                         ✅ implemented

GET    /api/ambient/v1/role_bindings                                      ✅ implemented
GET    /api/ambient/v1/role_bindings/{id}                                 ✅ implemented
POST   /api/ambient/v1/role_bindings                                      ✅ implemented
PATCH  /api/ambient/v1/role_bindings/{id}                                 ✅ implemented
DELETE /api/ambient/v1/role_bindings/{id}                                 ✅ implemented

GET    /api/ambient/v1/projects/{id}/agents/{agent_id}/role_bindings      ✅ implemented
GET    /api/ambient/v1/users/{id}/role_bindings                           🔲 planned
GET    /api/ambient/v1/projects/{id}/role_bindings                        🔲 planned
GET    /api/ambient/v1/sessions/{id}/role_bindings                        🔲 planned
GET    /api/ambient/v1/credentials/{cred_id}/role_bindings                🔲 planned

The credential:token-reader role is platform-internal. Credential CRUD is governed by RoleBindings with credential scope. See Security Spec — Token Reader Role Grant for grant semantics and runtime authorization rules.


ScheduledSessions (Project-Scoped)

GET    /api/ambient/v1/projects/{id}/scheduled-sessions                              list
POST   /api/ambient/v1/projects/{id}/scheduled-sessions                              create
GET    /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}                   read
PATCH  /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}                   update (schedule, session_prompt, enabled, timezone, description)
DELETE /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}                   delete

POST   /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}/suspend           disable — sets enabled=false
POST   /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}/resume            enable  — sets enabled=true
POST   /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}/trigger           immediate one-off ignite outside cron schedule
GET    /api/ambient/v1/projects/{id}/scheduled-sessions/{sched_id}/runs              list Sessions triggered by this schedule

Generic Proxy

All backend paths not mapped to a native /api/ambient/v1/... endpoint are forwarded verbatim to the backend service. See Security Spec — Proxy Authentication for authentication and credential injection behavior.

This allows SDK and CLI clients to reach the full backend surface through a single authenticated endpoint without requiring every backend route to be natively implemented in the API server. Routes listed here are candidates for future native spec entries.

Project Configuration (proxied)

GET    PUT          /api/projects/{p}/permissions
GET    POST DELETE  /api/projects/{p}/keys
GET    PUT          /api/projects/{p}/mcp-servers
GET    PUT          /api/projects/{p}/runner-secrets
GET    PUT          /api/projects/{p}/integration-secrets
GET                 /api/projects/{p}/secrets
GET    PUT POST DELETE  /api/projects/{p}/feature-flags[/{flagName}[/override|/enable|/disable]]
GET                 /api/projects/{p}/feature-flags/evaluate/{flagName}
GET                 /api/projects/{p}/runner-types
GET                 /api/projects/{p}/models
GET                 /api/projects/{p}/integration-status
GET                 /api/projects/{p}/access

Repository Operations (proxied)

GET                 /api/projects/{p}/repo/tree
GET                 /api/projects/{p}/repo/blob
GET                 /api/projects/{p}/repo/branches
GET                 /api/projects/{p}/repo/seed-status
POST                /api/projects/{p}/repo/seed
GET    POST         /api/projects/{p}/users/forks

Auth Integration Flows (proxied — admin)

*                   /api/auth/github/*
*                   /api/auth/google/*
*                   /api/auth/jira/*
*                   /api/auth/gitlab/*
*                   /api/auth/gerrit/*
*                   /api/auth/coderabbit/*
*                   /api/auth/mcp/*
GET    POST         /oauth2callback
GET                 /oauth2callback/status

Session Runtime — Runner-Internal (proxied)

These endpoints are called by runner pods at runtime. They are accessible via the API server for SDK/CLI tooling but are not intended for human interactive use.

POST                /api/projects/{p}/agentic-sessions/{s}/github/token
GET                 /api/projects/{p}/agentic-sessions/{s}/credentials/{provider}
POST                /api/projects/{p}/agentic-sessions/{s}/runner/feedback

Cluster / Platform (proxied)

GET                 /api/cluster-info
GET                 /api/version
GET                 /health
GET                 /api/runner-types
GET                 /api/workflows/ootb
GET                 /api/ldap/users[/{uid}]
GET                 /api/ldap/groups

Labels and Annotations

Every first-class Kind carries two JSONB columns:

ColumnPurposeExample values
labelsQueryable key/value tags. Use for filtering, grouping, and selection.{"env": "prod", "team": "platform", "tier": "critical"}
annotationsFreeform key/value metadata. Use for tooling notes, human remarks, external references.{"last-reviewed": "2026-03-21", "jira": "PLAT-123", "owner-slack": "@mturansk"}

Kinds with labels + annotations: User, Project, Agent, Session, Credential (global), Application

Kinds without: Inbox (ephemeral message queue), SessionMessage (append-only event stream), Role, RoleBinding (RBAC internals — structured by design)

Design: JSONB over EAV or separate tables

Instead of a separate metadata table (requires joins) or a polymorphic EAV table (breaks referential integrity), metadata is stored inline in the row it describes. This is the modern hybrid approach:

  • Zero joins: Data is co-located with the resource.
  • Infinite flexibility: Every row can carry different keys — no schema migration required to add a new label key.
  • GIN-indexed: PostgreSQL JSONB supports GIN (Generalized Inverted Index), making containment queries (@>) nearly as fast as standard column lookups at scale.
CREATE INDEX idx_projects_labels     ON projects     USING GIN (labels);
CREATE INDEX idx_agents_labels       ON agents       USING GIN (labels);
CREATE INDEX idx_sessions_labels     ON sessions     USING GIN (labels);
CREATE INDEX idx_credentials_labels  ON credentials  USING GIN (labels);

Query patterns

-- Find all sessions tagged env=prod
SELECT * FROM sessions WHERE labels @> '{"env": "prod"}';

-- Find all Agents owned by a team
SELECT * FROM agents WHERE labels @> '{"team": "platform"}';

-- Read a single annotation
SELECT annotations->>'jira' FROM projects WHERE id = 'my-project';

Convention

  • labels keys should be short, lowercase, hyphenated (e.g. env, team, tier, managed-by).
  • annotations keys should use reverse-DNS namespacing for tooling (e.g. ambient.io/last-sync, github.com/pr).
  • Neither column enforces a schema — validation is the caller's responsibility.
  • Default value: {} (empty object). Never null.

The Model as a String Tree

Every node in this model is an ID and a string. That is the complete primitive.

A Project is an ID and a prompt string — the workspace context. An Agent is an ID and a prompt string — who the agent is. A Session is an ID and a prompt string — what this run is focused on. An InboxMessage is an ID and a body string — a request addressed to an agent. A SessionMessage is an ID and a payload string — one turn in the conversation.

Strings can be simple ("hello world") or arbitrarily complex (a bookmarked system prompt, a structured markdown context block, a multi-section briefing). The model does not care. Every node is still just an ID and a string.

This means the entire data model is a composable JSON tree — four nodes, each an ID and a string:

{
  "project": {
    "id": "ambient-platform",
    "prompt": "This workspace builds the Ambient platform API server in Go. All agents operate on the same codebase. Prefer small, focused PRs. All code must pass gofmt, go vet, and golangci-lint before commit.",
    "labels": { "env": "prod", "team": "platform" },
    "annotations": { "github.com/repo": "ambient/platform" }
  },
  "agent": {
    "id": "01HXYZ...",
    "name": "be",
    "prompt": "You are a backend engineer specializing in Go REST APIs and Kubernetes operators. You write idiomatic Go, prefer explicit error handling over panic, and follow the plugin architecture in components/ambient-api-server/plugins/. You never use the service account client directly — always GetK8sClientsForRequest.",
    "labels": { "role": "backend", "lang": "go" },
    "annotations": { "ambient.io/specialty": "grpc,rest,k8s" }
  },
  "inbox": [
    {
      "id": "01HDEF...",
      "from": "overlord",
      "body": "While you're in the sessions plugin, also harden the subresource handler — agent_id is interpolated directly into a TSL search string."
    },
    {
      "id": "01HGHI...",
      "from": null,
      "body": "The presenter nil-pointer in projectAgents and inbox needs a guard before this goes to staging."
    }
  ],
  "session": {
    "id": "01HABC...",
    "prompt": "Implement WatchSessionMessages gRPC handler with SSE fan-out and replay. Replay all existing messages to new subscribers before switching to live delivery. Repo: github.com/ambient/platform, path: components/ambient-api-server/plugins/sessions/.",
    "labels": { "wave": "3", "feature": "session-messages" },
    "annotations": { "github.com/pr": "ambient/platform#142" }
  },
  "message": {
    "event_type": "user",
    "payload": "Begin. Start with the gRPC handler, then wire SSE, then write the integration test."
  }
}

Composition

Because every node is a string, entire agent suites and workspaces compose declaratively.

The start context pipeline is string composition — each scope inherits and narrows the string above it:

Project.prompt        → workspace context (shared by all agents)
  Agent.prompt        → who this agent is
    Inbox messages    → what others have asked (queued intent)
      Session.prompt  → what this run is focused on

To compose a new workspace: write a Project.prompt. To define a new agent role: write an Agent.prompt and create the Agent in the project. To start: the system assembles the full context string automatically, in order, from the tree.

A different Project.prompt = a different team with different shared context. An Agent with the same name in two projects = the same role operating in two different workspaces (separate records, independently mutable). A poke (InboxMessage.body) sent from one Agent to another = a string crossing a node boundary.

This structure means you can define and compose bespoke agent suites — entire fleets with different roles, different workspace contexts, different session scopes — purely by composing strings at the right node in the tree. The platform assembles the start context; the model does the rest.


Design Decisions

DecisionRationale
Agent is project-scoped, not globalSimplicity. An agent's identity and prompt are contextual to the project it serves. No indirection via a global registry.
Agent.prompt is mutablePrompt editing is a routine operational task. RBAC controls who can change it. No versioning overhead.
Agent ownership via RBAC, not a hardcoded FKOwnership is expressed as a RoleBinding (scope=agent, agent_id=<id>, user_id=<owner>). Enables multi-owner and delegated ownership consistently across all Kinds.
One active Session per AgentAvoids concurrent conflicting runs; start is idempotent
Inbox on Agent, not SessionMessages persist across re-ignitions; addressed to the agent, not the run
Inbox drained at startUnread messages become part of the start context; session picks up where things left off
current_session_id denormalized on AgentProject Home reads Agent + session phase without joining through sessions
Sessions created only via startSessions are run artifacts; direct POST /sessions does not exist
Every layer carries a promptProject.prompt = workspace context; Agent.prompt = who the agent is; Session.prompt = what this run does; Inbox = prior requests. Pokes roll downhill.
SessionMessage is append-onlyCanonical record of the LLM conversation; never edited or deleted
CLI mirrors API 1-for-1Every endpoint has a corresponding command; status tracked explicitly
This document is the specA reconciler will compare the spec (this doc) against code status and surface gaps
labels / annotations are JSONB, not stringsEnables GIN-indexed key/value queries (@> operator) without joins; every row carries its own metadata without a separate EAV table. labels = queryable tags; annotations = freeform notes. Applied to first-class Kinds: User, Project, Agent, Session. Not applied to Inbox, SessionMessage, Role/RoleBinding.
Credential is global, not project-scopedEliminates duplication when the same PAT is used across multiple Projects. Access controlled via RoleBindings with credential scope. A single Credential can be shared across Projects without creating copies.
Application syncs fleet definitions, not infrastructureApplication syncs Projects, Agents, Credentials, RoleBindings, and Inbox seeds. Sessions, Users, and Roles are not synced.
Application targets Ambient API, not K8s APIUnlike Sessions (which use kubeconfig for direct K8s provisioning), Application works at the Ambient REST API layer. Remote sync uses the SDK client pointed at destination_ambient_url.
Promotion via multiple ApplicationsEach environment gets its own Application pointing to a different git overlay and destination Ambient URL. Promotion = merge changes between overlay branches.
Kustomize engine shared between CLI and API serverThe sync engine reuses the same kustomize rendering logic as acpctl apply -k.
Git polling, not webhooks (v1)Simplicity. Webhook-triggered refresh is a v2 optimization.
Self-heal is opt-inDefault false. When enabled, the controller detects and reverts drift — useful for production fleets where UI-based changes should not persist.
Sync engine bound by credential escalation rulesThe sync engine can only create RoleBindings where the role level is at or below the level of the service credential it authenticates with. This prevents a compromised git repo from escalating RBAC in the destination project. The credential's effective role level sets the ceiling. A sync that attempts to create a binding above the ceiling fails with a per-resource Forbidden status in resource_status.
Remote Ambient auth via stored Credential, not forwarded tokenAsync polling controllers (auto_sync) have no request context. The credential_id FK on Application provides the auth context. Token is resolved at sync time via GET /credentials/{id}/token, never cached beyond a single sync cycle.
Project prune requires manual confirmationauto_prune deletes Agents and sub-resources automatically, but never auto-prunes a Project. Project removal is permanently destructive (cascades through Agents, Sessions, Inbox, SessionMessages). Pruning a Project requires explicit POST /sync with prune: true, prune_project: true.
gitops:admin is platform-scopedApplications can target any Ambient instance, including production environments. Cross-environment reach exceeds project scope, so gitops:admin is grantable only by platform:admin. project:owner can create Applications where destination_ambient_url is null (local) and destination_project matches a project they own. This allows teams to self-serve GitOps for their own projects without platform-admin escalation.
gitops:admin / gitops:viewer follow platform escalation chainOnly platform:admin can grant gitops:admin or gitops:viewer. project:owner cannot grant these roles. This matches the escalation pattern established for credential:owner and other platform-scoped roles in the security spec.
Unsupported kinds silently skipped by sync engineThe kustomize engine supports all apply kinds (including Cluster, Ambient). The sync engine intentionally syncs only fleet definition kinds (Project, Agent, Credential, RoleBinding, Inbox). Documents of other kinds are silently skipped with a Skipped status in resource_status, not treated as errors. This allows shared kustomize overlays to contain infrastructure inventory alongside fleet definitions without breaking sync.

Security and credential design decisions (RBAC scoping, write-only tokens, role catalog rationale) are in the Security Spec — Design Decisions.


Credential — Usage

# Create a GitLab PAT — token via env var (avoids shell history exposure)
acpctl credential create --name my-gitlab-pat --provider gitlab \
  --token "$GITLAB_PAT" --url https://gitlab.myco.com
# credential/my-gitlab-pat created

# Token via stdin (also avoids shell history)
echo "$GITLAB_PAT" | acpctl credential create --name my-gitlab-pat --provider gitlab \
  --token @- --url https://gitlab.myco.com

# Bind credential to a project (grants access to all agents in the project)
acpctl credential bind my-gitlab-pat --project my-project

# Bind the same credential to another project (no duplication)
acpctl credential bind my-gitlab-pat --project other-project

# List credentials (filtered by caller's RoleBindings)
acpctl credential list
# NAME              PROVIDER  URL                      CREATED
# my-gitlab-pat     gitlab    https://gitlab.myco.com  2026-03-31

# Rotate a token
acpctl credential update my-gitlab-pat --token "$GITLAB_PAT_NEW"

# Declarative apply — token sourced from env var
kind: Credential
metadata:
  name: platform-gitlab-pat
spec:
  provider: gitlab
  token: $GITLAB_PAT
  url: https://gitlab.myco.com
  labels:
    team: platform
acpctl apply -f credential.yaml
# credential/platform-gitlab-pat created

# Then bind to the desired project
acpctl credential bind platform-gitlab-pat --project my-project

Design Decisions — Credential

Credentials are global resources, not project-scoped. This eliminates duplication when the same PAT is used across multiple Projects. Access is controlled via RoleBindings — bind a credential to a project scope to grant access to all agents in that project.

See the Security Spec — Design Decisions for credential design rationale (storage, rotation, provider serialization, migration).


Implementation Coverage Matrix

Last updated: 2026-04-28. Use this as the authoritative index — click into component source to verify.

AreaAPI ServerGo SDKCLI (acpctl)Notes
Sessions — CRUDSessionAPI.{Get,List,Create,Update,Delete}get/create/delete session
Sessions — start/stop/start /stopSessionAPI.{Start,Stop}start/stop commands
Sessions — messages (list/push/watch)/messagesPushMessage, ListMessages, WatchSessionMessages (gRPC)session messages, session sendgRPC watch via session_watch.go
Session messages (top-level)GET /session_messagesSessionMessages().List()n/aSDK/CP-internal; used by CP to resolve max seq on restart
Sessions — live events (SSE proxy)/events → runner podSessionAPI.StreamEventsio.ReadClosersession eventsRunner must be Running; 502 if unreachable
Sessions — labels/annotations✅ PATCH accepts labels/annotations✅ fields on Session type; SessionAPI.Update(patch map[string]any)⚠️ no dedicated subcommand; use acpctl get session -o json + manual PATCH
Sessions — workspace files✅ sessions plugin; stubs empty list when no runner; 503 per-file-op🔲🔲 session workspace list/get/put/deleteRequires running session for file ops
Sessions — pre-upload files✅ sessions plugin; stubs empty list when no runner; 503 per-file-op🔲🔲 session files list/upload/deleteS3-staged; available before session starts
Sessions — git✅ sessions plugin; stubs empty status/branches; configure-remote 503 if no runner🔲🔲 session git status/configure-remote/branches
Sessions — repos✅ sessions plugin; repos/status stub; add/remove stored natively in session DB🔲🔲 session repos list/add/remove
Sessions — operational✅ sessions plugin; clone/displayname/model/workflow/export/pod-events native; oauth 501🔲🔲 session clone/model/export/pod-events
Sessions — runner protocol✅ sessions plugin; agui/{run,events,interrupt,feedback,tasks,capabilities}, mcp/status🔲🔲 session interrupt/feedback/capabilities/tasksAGUI prefix routes; 502 if runner unreachable
Agents — CRUD/projects/{id}/agentsProjectAgentAPI.{ListByProject,GetByProject,GetInProject,CreateInProject,UpdateInProject,DeleteInProject}agent list/get/create/update/delete
Agents — start/start-preview/startProjectAgentAPI.{Start,GetStartPreview}start <id>, agent start-previewIdempotent — returns existing session if active
Agents — sessions history/sessions sub-resourceProjectAgentAPI.Sessionsagent sessionsReturns SessionList scoped to agent
Agents — labels/annotations✅ PATCH accepts labels/annotations✅ fields on ProjectAgent type; UpdateInProject(patch map[string]any)⚠️ via agent update with raw patch; no typed helpers
Inbox — list/send✅ GET/POST /inboxInboxMessageAPI.{ListByAgent,Send} + ProjectAgentAPI.{ListInboxInProject,SendInboxInProject}inbox list, inbox send
Inbox — mark-read/delete✅ PATCH/DELETE /inbox/{id}InboxMessageAPI.{MarkRead,DeleteMessage}inbox mark-read, inbox delete
Projects — CRUDProjectAPI.{Get,List,Create,Update,Delete}get/create/delete project, project set/current, project update
Projects — labels/annotations✅ PATCH accepts labels/annotations✅ fields on Project type; ProjectAPI.Update(patch map[string]any)⚠️ no dedicated subcommand
RBAC — roles✅ full CRUDRoleAPIcreate role, get roles, get roles <id>, delete role
RBAC — role bindings✅ full CRUDRoleBindingAPIcreate role-binding, get role-bindings, get role-bindings <id>, delete role-binding
RBAC — scoped role_bindings queries✅ agents only; 🔲 users/projects/sessions/credentialsn/an/aGET /projects/{id}/agents/{agent_id}/role_bindings implemented; other 4 scoped endpoints not yet
Credentials — CRUDplugins/credentials/ (global at /credentials)credential_api.go + credential_extensions.gocredential list/get/create/update/delete/tokencredential bind not yet implemented.
Credentials — token fetchGET /credentials/{cred_id}/tokenGetToken() in credential_extensions.gocredential token <id>Gated by credential:token-reader; granted to runner SA by operator
ScheduledSessions — CRUD✅ scheduledSessions pluginScheduledSessionAPI.{List,Get,Create,Update,Delete,GetByName}scheduled-session list/get/create/update/delete
ScheduledSessions — lifecycle✅ suspend/resume/trigger/runs handlersScheduledSessionAPI.{Suspend,Resume,Trigger,Runs}scheduled-session suspend/resume/trigger/runs
Generic proxy — project config✅ proxy plugin (plugins/proxy); forwards non-/api/ambient/ paths to BACKEND_URLn/a🔲 raw HTTP fallbackPermissions, keys, MCP servers, secrets, feature flags
Generic proxy — repo operations✅ proxy pluginn/a🔲 raw HTTP fallbackTree, blob, branches, seed, forks
Generic proxy — auth integrations✅ proxy pluginn/an/aGitHub/GitLab/Google/Jira/Gerrit/CodeRabbit/MCP OAuth flows
Generic proxy — cluster/platform✅ proxy pluginn/a🔲 acpctl version, acpctl cluster-infocluster-info, version, health, LDAP, OOTB workflows
Declarative applyn/auses SDKapply -f, apply -kUpsert semantics; supports inbox seeding
Declarative apply — Credential kindn/auses SDKapply -f credential.yamlGlobal resource; token sourced from env var in YAML
Declarative apply — ScheduledSession kindn/a🔲🔲Planned; schedule and agent reference in YAML
Applications — CRUD🔲 planned🔲 planned🔲 plannedGitOps sync binding
Applications — sync/refresh🔲 planned🔲 planned🔲 plannedTrigger sync or refresh operations
Applications — status🔲 planned🔲 planned🔲 plannedPer-resource sync/health detail

Labels/Annotations — SDK Ergonomics Gap

All Kinds with labels/annotations store them as JSON strings in the DB (*string in the Go model) but as structured maps in the OpenAPI schema. The Go SDK type carries Labels *string / Annotations *string (matching the DB column). Consumers doing label/annotation operations must marshal/unmarshal the JSON string themselves — there are no typed PatchLabels/PatchAnnotations helper methods in the SDK.

Workaround: Use Update(ctx, id, map[string]any{"labels": labelsMap, "annotations": annotationsMap}). The API server accepts the map directly and stores it as JSON.

Permanent fix: Add PatchLabels / PatchAnnotations typed helpers to SessionAPI, ProjectAgentAPI, and ProjectAPI in the SDK — these should accept map[string]string and call Update internally.

CLI — Known Gaps vs Spec

CommandStatusPath to close
Project/Agent/Session label subcommands🔲 no acpctl label/acpctl annotateadd typed label helpers to SDK first, then CLI
acpctl credential bind🔲 not implementedPOST /role_bindings with scope=credential; global migration complete, command not yet written
Session workspace/files/git/repos subcommands🔲 plannedsee Session Operations table above

Manual Test

1. Project

acpctl create project --name test-cred-1 --description "cred test" acpctl project test-cred-1

2. Agent

acpctl agent create --project-id test-cred-1 --name github-agent
--prompt "You are a GitHub automation agent."

AGENT_ID=(acpctlagentlistprojectidtestcred1ojsonpython3c"importsys,json;print(json.load(sys.stdin)[items][0][id])")echo"AGENTID=(acpctl agent list --project-id test-cred-1 -o json | python3 -c "import sys,json; print(json.load(sys.stdin)['items'][0]['id'])") echo "AGENT_ID=AGENT_ID"

3. Credential (global resource)

printf 'kind: Credential\nname: github-pat-test\nprovider: github\ntoken: %s\ndescription: test\n'
"$(cat ~/projects/secrets/github.ambient-pat.token)" > /tmp/cred.yaml acpctl apply -f /tmp/cred.yaml && rm /tmp/cred.yaml

4. Bind credential to project

acpctl credential bind github-pat-test --project test-cred-1

CRED_ID=(acpctlcredentiallistojsonpython3c"importsys,json;print(next(i[id]foriinjson.load(sys.stdin)[items]ifi[name]==githubpattest))")echo"CREDID=(acpctl credential list -o json | python3 -c "import sys,json; print(next(i['id'] for i in json.load(sys.stdin)['items'] if i['name']=='github-pat-test'))") echo "CRED_ID=CRED_ID"

5. Start session

SESSION_ID=(acpctlstartgithubagentprojectidtestcred1 prompt"Fetchcredential(acpctl start github-agent --project-id test-cred-1 \ --prompt "Fetch credential CRED_ID token and confirm you received it."
-o json | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "SESSION_ID=$SESSION_ID"

6. Watch events

acpctl session events "$SESSION_ID"