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
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.
promptdefines 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.
%%{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 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.
| Argo CD Concept | Ambient Equivalent | Description |
|---|---|---|
| Application | Application | Declarative binding of source → destination |
| Source (repo + path + revision) | source_repo_url + source_path + source_target_revision |
Git repo containing kustomize overlays of Projects, Agents, Credentials, RoleBindings |
| Application Source Type | Always Kustomize | The CLI's built-in kustomize engine renders the manifests |
| Destination (cluster + namespace) | destination_ambient_url + destination_project |
Target Ambient instance + project name |
| Target State | Rendered kustomize output | The desired set of Projects, Agents, Credentials, RoleBindings, and Inbox seeds from git |
| Live State | Current API server state | What actually exists in the destination Ambient's project |
| Sync Status | sync_status |
Whether live state matches target state: Synced, OutOfSync, Unknown |
| Sync Operation | /sync sub-resource |
The act of applying target state to live state |
| Refresh | /refresh sub-resource |
Fetch latest from git, render kustomize, diff against live state |
| Health | health_status |
Are all synced agents healthy? Healthy, Degraded, Progressing, Unknown |
| Self-Heal | self_heal flag |
Re-sync when live state drifts (agent modified via UI, deleted manually) |
| Prune | auto_prune flag |
Delete agents/resources from Ambient that no longer exist in git |
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):
| Kind | Sync Behavior |
|---|---|
Project |
Created if CreateProject=true in sync_options; patched (description, prompt, labels, annotations) on subsequent syncs |
Agent |
Created or patched within the destination project; prompt, labels, annotations updated |
Credential |
Created if not present; idempotent by name |
RoleBinding |
Created 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. |
| Kind | Why |
|---|---|
Session |
Ephemeral run artifact. Created via agent start, not via GitOps. |
SessionMessage |
Append-only event stream. |
ScheduledSession |
Project-scoped trigger config; future sync candidate. |
User |
Identity record. |
Role |
RBAC definition (platform-scoped, not project-scoped). |
| Field | Notes |
|---|---|
name |
Unique, human-readable. The stable address of this sync binding. |
source_repo_url |
Git repository URL. HTTPS or SSH. |
source_target_revision |
Branch name, tag, or commit SHA. Default: main. |
source_path |
Relative path within the repo to a kustomize directory (must contain kustomization.yaml). |
credential_id |
Nullable 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_url |
Nullable. 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_project |
Target project name. The project is created on first sync if CreateProject=true is in sync_options. |
auto_sync |
If true, the controller polls the git repo and syncs automatically when changes are detected. If false, sync is manual via POST /sync. |
auto_prune |
If 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_heal |
If 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_options |
Comma-separated option flags. Initial options: CreateProject=true. |
retry_limit |
Max number of automatic retries on sync failure. Default: 3. |
sync_status |
Computed on refresh. Synced = live matches target. OutOfSync = differences detected. Unknown = not yet refreshed. |
health_status |
Computed 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_revision |
The git commit SHA of the last successful sync. |
operation_phase |
State of the last sync operation: Succeeded, Failed, Running, or empty if never synced. |
operation_message |
Human-readable summary, e.g. "3 created, 1 configured, 0 pruned". |
resource_status |
JSONB array of per-resource sync results: [{"kind": "Agent", "name": "lead", "status": "Synced", "health": "Healthy", "message": "configured"}]. |
conditions |
JSONB array of error conditions: [{"type": "SyncError", "message": "...", "lastTransitionTime": "..."}]. |
last_synced_at |
Timestamp of the last successful sync completion. |
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.
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}/tokenWhen 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.
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.
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: falsePromotion is a git operation: merge the dev overlay changes into the release branch, then sync the staging Application.
Agent is scoped to a Project. The stable address is {project_name}/{agent_name}.
| Field | Notes |
|---|---|
name |
Human-readable, unique within the project. Used as display name and in addressing. |
display_name |
Nullable. Human-friendly label for UI display; does not affect addressing. |
description |
Nullable. Free-text purpose description. |
prompt |
Defines who the agent is. Mutable via PATCH. Access controlled by RBAC (agent:editor or higher). |
parent_agent_id |
Nullable FK. Set when this agent was spawned as a sub-agent by another agent. |
owner_user_id |
FK to the User who owns this agent. Set at creation; matches the authenticated caller. |
repo_url |
Nullable. Primary repository URL cloned into every session the agent starts. Copied to Session.repo_url on ignite. |
workflow_id |
Nullable. Default workflow identifier injected into sessions. Copied to Session.workflow_id on ignite. |
llm_model |
Active LLM model name. Default: claude-sonnet-4-6. Copied to Session.llm_model on ignite. |
llm_temperature |
LLM sampling temperature. Default: 0.7. Copied to Session.llm_temperature on ignite. |
llm_max_tokens |
Max tokens per LLM response. int32, default: 4000. Copied to Session.llm_max_tokens on ignite. |
bot_account_name |
Nullable. Service account name for git operations inside sessions. Copied to Session.bot_account_name on ignite. |
resource_overrides |
Nullable. JSON-encoded pod resource requests/limits override for sessions spawned by this agent. Copied to Session.resource_overrides on ignite. |
environment_variables |
Nullable. JSON-encoded extra environment variables injected into session pods. Copied to Session.environment_variables on ignite. |
current_session_id |
Denormalized 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 deleteOnly 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 messages are addressed to an Agent (agent_id). They are distinct from Session Messages:
| Inbox | SessionMessage | |
|---|---|---|
| Scope | Agent (persists across sessions) | Session (ephemeral) |
| Created by | Human or another Agent | LLM turn / runner gRPC push |
| Drained | At session start | Never — append-only stream |
| Purpose | Queued intent waiting for next run | Real 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.
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.
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.
| Endpoint | Source | Persistence | Purpose |
|---|---|---|---|
GET /sessions/{id}/messages |
API server gRPC fan-out | Persisted in DB (replay from seq=0) |
Durable stream; supports replay and history |
GET /sessions/{id}/events |
Runner pod SSE (GET /events/{thread_id}) |
Ephemeral; runner-local in-memory queue | Live 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.
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.
| Field | Notes |
|---|---|
name |
Human-readable, unique within the project. |
agent_id |
Which Agent to ignite. Must exist in the same project. |
schedule |
Standard cron expression (e.g. "0 9 * * 1-5" = 9 AM on weekdays). |
timezone |
IANA timezone string (e.g. "America/New_York"). Defaults to UTC. |
enabled |
false suspends evaluation without deleting the schedule. |
session_prompt |
Injected as Session.prompt on each trigger — the recurring task. |
last_run_at |
Wall-clock time of the last trigger. Null if never triggered. |
next_run_at |
Computed 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}.
The acpctl CLI mirrors the API 1-for-1. Every REST operation has a corresponding command.
| REST API | acpctl Command |
Status |
|---|---|---|
GET /projects |
acpctl get projects |
✅ implemented |
GET /projects/{id} |
acpctl get project <name> |
✅ implemented |
POST /projects |
acpctl 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 |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /projects/{id}/agents |
acpctl 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}/agents |
acpctl 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}/start |
acpctl start <agent-id> --project-id <p> [--prompt <t>] |
✅ implemented |
GET /projects/{id}/agents/{agent_id}/start |
acpctl agent start-preview --project-id <p> --agent-id <id> |
✅ implemented |
GET /projects/{id}/agents/{agent_id}/sessions |
acpctl agent sessions --project-id <p> --agent-id <id> |
✅ implemented |
GET /projects/{id}/agents/{agent_id}/inbox |
acpctl inbox list --project-id <p> --pa-id <id> |
✅ implemented |
POST /projects/{id}/agents/{agent_id}/inbox |
acpctl 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 |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /sessions |
acpctl get sessions |
✅ implemented |
GET /sessions |
acpctl 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}/messages |
acpctl session messages <id> |
✅ implemented |
POST /sessions/{id}/messages |
acpctl session send <id> <message> |
✅ implemented |
POST /sessions/{id}/messages + GET /sessions/{id}/events |
acpctl session send <id> <message> -f |
✅ implemented |
POST /sessions/{id}/messages + GET /sessions/{id}/events |
acpctl session send <id> <message> -f --json |
✅ implemented |
GET /sessions/{id}/events |
acpctl session events <id> |
✅ implemented |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /projects/{id}/scheduled-sessions |
acpctl scheduled-session list |
✅ implemented |
GET /projects/{id}/scheduled-sessions/{sched_id} |
acpctl scheduled-session get <name> |
✅ implemented |
POST /projects/{id}/scheduled-sessions |
acpctl 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 .../suspend |
acpctl scheduled-session suspend <name> |
✅ implemented |
POST .../resume |
acpctl scheduled-session resume <name> |
✅ implemented |
POST .../trigger |
acpctl scheduled-session trigger <name> |
✅ implemented |
GET .../runs |
acpctl scheduled-session runs <name> |
✅ implemented |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /sessions/{id}/workspace |
acpctl session workspace list <id> |
🔲 planned |
GET /sessions/{id}/workspace/*path |
acpctl session workspace get <id> <path> |
🔲 planned |
PUT /sessions/{id}/workspace/*path |
acpctl session workspace put <id> <path> [--file <f>] |
🔲 planned |
DELETE /sessions/{id}/workspace/*path |
acpctl session workspace delete <id> <path> |
🔲 planned |
GET /sessions/{id}/files |
acpctl session files list <id> |
🔲 planned |
PUT /sessions/{id}/files/*path |
acpctl session files upload <id> <path> [--file <f>] |
🔲 planned |
DELETE /sessions/{id}/files/*path |
acpctl session files delete <id> <path> |
🔲 planned |
GET /sessions/{id}/git/status |
acpctl session git status <id> |
🔲 planned |
POST /sessions/{id}/git/configure-remote |
acpctl session git configure-remote <id> |
🔲 planned |
GET /sessions/{id}/git/branches |
acpctl session git branches <id> |
🔲 planned |
GET /sessions/{id}/repos/status |
acpctl session repos list <id> |
🔲 planned |
POST /sessions/{id}/repos |
acpctl session repos add <id> --repo <url> |
🔲 planned |
DELETE /sessions/{id}/repos/{name} |
acpctl session repos remove <id> <repo> |
🔲 planned |
POST /sessions/{id}/clone |
acpctl session clone <id> [--name <n>] |
🔲 planned |
POST /sessions/{id}/model |
acpctl session model <id> --model <m> |
🔲 planned |
GET /sessions/{id}/export |
acpctl session export <id> |
🔲 planned |
GET /sessions/{id}/pod-events |
acpctl session pod-events <id> |
🔲 planned |
GET /sessions/{id}/tasks |
acpctl session tasks <id> |
🔲 planned |
POST /sessions/{id}/tasks/{task_id}/stop |
acpctl session tasks stop <id> <task-id> |
🔲 planned |
GET /sessions/{id}/tasks/{task_id}/output |
acpctl session tasks output <id> <task-id> |
🔲 planned |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /applications |
acpctl get applications |
🔲 planned |
GET /applications/{id} |
acpctl get application <name> |
🔲 planned |
POST /applications |
acpctl 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}/sync |
acpctl sync application <name> [--prune] [--revision <r>] |
🔲 planned |
POST /applications/{id}/refresh |
acpctl refresh application <name> |
🔲 planned |
GET /applications/{id}/status |
acpctl get application <name> -o wide |
🔲 planned |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /credentials |
acpctl credential list [--provider <p>] |
✅ implemented |
POST /credentials |
acpctl 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}/token |
acpctl credential token <id> |
✅ implemented |
POST /role_bindings |
acpctl credential bind <cred-name> --project <project> |
✅ implemented |
| REST API | acpctl Command |
Status |
|---|---|---|
GET /roles |
acpctl get roles |
✅ implemented |
GET /roles/{id} |
acpctl get roles <id> |
✅ implemented |
POST /roles |
acpctl create role --name <n> [--permissions <json>] |
✅ implemented |
DELETE /roles/{id} |
acpctl delete role <id> |
✅ implemented |
GET /role_bindings |
acpctl get role-bindings |
✅ implemented |
GET /role_bindings/{id} |
acpctl get role-bindings <id> |
✅ implemented |
POST /role_bindings |
acpctl 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 |
| Operation | acpctl Command |
Status |
|---|---|---|
| Authenticate | acpctl login [SERVER_URL] --token <t> |
✅ implemented |
| Log out | acpctl logout |
✅ implemented |
| Identity | acpctl whoami |
✅ implemented |
| Config get | acpctl config get <key> |
✅ implemented |
| Config set | acpctl config set <key> <value> |
✅ implemented |
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.
| Kind | Fields applied |
|---|---|
Project |
name, description, prompt, labels, annotations |
Agent |
name, prompt, labels, annotations, inbox (seed messages) |
Credential |
name, 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.
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 stdinEach 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
namealready exists,PATCHit (description, prompt, labels, annotations). If it does not exist,POSTto create it. - Agent: resolved within the current project context. If an agent with
namealready exists in the project,PATCHit (prompt, labels, annotations). If it does not exist,POSTto 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 createdWith -o json: JSON array of all applied resources.
acpctl apply -k <dir> # build kustomization in <dir> and apply the resultEquivalent 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 resourcesPatches use strategic merge: scalar fields overwrite, maps merge, sequences replace.
Output is identical to -f.
# 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 -| Flag | Description |
|---|---|
-f <path> |
File, directory, or - for stdin. Mutually exclusive with -k. |
-k <dir> |
Kustomize directory. Mutually exclusive with -f. |
--dry-run |
Print what would be applied without making API calls. |
-o json |
JSON output (array of applied resources). |
--project <name> |
Override project context for Agent resources. |
| Output | Meaning |
|---|---|
created |
Resource did not exist; POST succeeded. |
configured |
Resource existed; PATCH applied one or more changes. |
unchanged |
Resource existed and matched desired state; no API call made. |
| Command | Status |
|---|---|
acpctl apply -f <path> |
✅ implemented |
acpctl apply -k <dir> |
✅ implemented |
| Flag | Description |
|---|---|
--insecure-skip-tls-verify |
Skip TLS certificate verification |
-o json |
JSON output (most get/create commands) |
-o wide |
Wide table output |
--limit <n> |
Max items to return (default: 100) |
-w / --watch |
Live watch mode (sessions only) |
--watch-timeout <duration> |
Watch timeout (default: 30m) |
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 overlordGET /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 projectGET /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 bindingsPOST /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:
Project.prompt(workspace context — shared by all agents in this project)Agent.prompt(who you are)- Drained Inbox messages (what others have asked you to do)
Session.prompt(what this run is focused on)- Peer Agent roster with latest status
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 bindingsGET /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:
| Parameter | Example | Purpose |
|---|---|---|
search |
session_id='01HABC...' |
TSL filter; must contain session_id = '...' |
orderBy |
seq desc |
Column + direction (seq asc, seq desc, created_at desc) |
size |
1 |
Max rows returned |
Example — fetch the latest message for a session:
GET /api/ambient/v1/session_messages?search=session_id='01HABC...'&orderBy=seq desc&size=1Response 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.
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 detailPOST /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.
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": []
}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 fileStage 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 fileGET /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 branchesAttach 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 repoPOST /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 transcriptThese 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 taskCredentials 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-readerNote:
credential bind(viaPOST /role_bindingswithscope=credential,credential_id, andproject_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 | Service | Token type | url |
email |
|---|---|---|---|---|
github |
GitHub.com or GitHub Enterprise | Personal Access Token | optional; required for GHE | — |
gitlab |
GitLab.com or self-hosted | Personal Access Token | optional; required for self-hosted | — |
jira |
Jira Cloud (Atlassian) | API Token | required (Atlassian instance URL) | required (used in Basic auth) |
google |
Google Cloud / Workspace | Service Account JSON serialized to string | — | — |
vertex |
Vertex AI (GCP) | GCP service account key | — | — |
kubeconfig |
Kubernetes clusters | Kubeconfig file serialized to string | — | — |
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.
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 value |
Non-null FK | Meaning |
|---|---|---|
global |
(none) | Role applies across the entire platform |
project |
project_id |
Role applies within a specific project |
agent |
agent_id |
Role applies to a specific agent |
session |
session_id |
Role applies to a specific session run |
credential |
credential_id |
Role 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 case | user_id |
scope FK | Meaning |
|---|---|---|---|
| User A owns Credential Y | user_id=A |
credential_id=Y |
A can CRUD credential Y |
| Credential Y bound to Project X | user_id=NULL |
credential_id=Y + project_id=X |
Project X can access credential Y |
| User A is project:owner of Project X | user_id=A |
project_id=X |
A owns project X |
| Global platform:admin grant | user_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.
| Scope | FK set | Meaning |
|---|---|---|
global |
(none) | Applies across the entire platform |
project |
project_id |
Applies to all resources in a specific project |
agent |
agent_id |
Applies to a specific Agent and all its sessions |
session |
session_id |
Applies to one session run only |
credential |
credential_id |
Governs access to a specific Credential |
Effective permissions = union of all applicable bindings (global ∪ project ∪ agent ∪ session). No deny rules.
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.
| Role | Description |
|---|---|
platform:admin |
Full access to everything |
platform:viewer |
Read-only across the platform |
project:owner |
Full control of a project and all its agents |
project:editor |
Create/update Agents, ignite, send messages |
project:viewer |
Read-only within a project |
agent:operator |
Ignite and message a specific Agent |
agent:editor |
Update prompt and metadata on a specific Agent |
agent:observer |
Read a specific Agent and its sessions |
agent:runner |
Minimum viable pod credential: read agent, push messages, send inbox |
credential:owner |
Full CRUD on credentials the user created. Bind credentials to projects the user has project:owner on. |
credential:viewer |
Read metadata (not token) on credentials bound to projects the user has access to. |
credential:token-reader |
Fetch 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:admin |
Full CRUD on Applications; trigger sync/refresh. Platform-scoped — grantable only by platform:admin. |
gitops:viewer |
Read-only on Applications and their status. Platform-scoped — grantable only by platform:admin. |
| Role | Projects | Agents | Sessions | Inbox | Credentials | Apps | Home | RBAC |
|---|---|---|---|---|---|---|---|---|
platform:admin |
full | full | full | full | full | full | full | full |
platform:viewer |
read/list | read/list | read/list | — | read/list | read/list | read | read/list |
project:owner |
full | full | full | full | manage bindings | local-only (own project) | read | project+agent bindings |
project:editor |
read | create/update/ignite | read/list | send/read | — | — | read | — |
project:viewer |
read | read/list | read/list | — | — | — | read | — |
gitops:admin |
— | — | — | — | — | full (any destination) | — | — |
gitops:viewer |
— | — | — | — | — | read/list | — | — |
agent:operator |
— | update/ignite | read/list | send/read | — | — | — | — |
agent:editor |
— | update | — | — | — | — | — | — |
agent:observer |
— | read | read/list | — | — | — | — | — |
agent:runner |
— | read | read | send | — | — | — | — |
credential:owner |
— | — | — | — | create/update/delete + bind | — | — | — |
credential:viewer |
— | — | — | — | read/list (metadata only) | — | — | — |
credential:token-reader |
— | — | — | — | token: read | — | — | — |
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 🔲 plannedThe 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.
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 scheduleAll 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.
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}/accessGET /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* /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/statusThese 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/feedbackGET /api/cluster-info
GET /api/version
GET /health
GET /api/runner-types
GET /api/workflows/ootb
GET /api/ldap/users[/{uid}]
GET /api/ldap/groupsEvery first-class Kind carries two JSONB columns:
| Column | Purpose | Example values |
|---|---|---|
labels |
Queryable key/value tags. Use for filtering, grouping, and selection. | {"env": "prod", "team": "platform", "tier": "critical"} |
annotations |
Freeform 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)
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);-- 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';labelskeys should be short, lowercase, hyphenated (e.g.env,team,tier,managed-by).annotationskeys 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). Nevernull.
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."
}
}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 onTo 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.
| Decision | Rationale |
|---|---|
| Agent is project-scoped, not global | Simplicity. An agent's identity and prompt are contextual to the project it serves. No indirection via a global registry. |
| Agent.prompt is mutable | Prompt editing is a routine operational task. RBAC controls who can change it. No versioning overhead. |
| Agent ownership via RBAC, not a hardcoded FK | Ownership 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 Agent | Avoids concurrent conflicting runs; start is idempotent |
| Inbox on Agent, not Session | Messages persist across re-ignitions; addressed to the agent, not the run |
| Inbox drained at start | Unread messages become part of the start context; session picks up where things left off |
current_session_id denormalized on Agent |
Project Home reads Agent + session phase without joining through sessions |
| Sessions created only via start | Sessions are run artifacts; direct POST /sessions does not exist |
Every layer carries a prompt |
Project.prompt = workspace context; Agent.prompt = who the agent is; Session.prompt = what this run does; Inbox = prior requests. Pokes roll downhill. |
SessionMessage is append-only |
Canonical record of the LLM conversation; never edited or deleted |
| CLI mirrors API 1-for-1 | Every endpoint has a corresponding command; status tracked explicitly |
| This document is the spec | A reconciler will compare the spec (this doc) against code status and surface gaps |
labels / annotations are JSONB, not strings |
Enables 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-scoped | Eliminates 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 infrastructure | Application syncs Projects, Agents, Credentials, RoleBindings, and Inbox seeds. Sessions, Users, and Roles are not synced. |
| Application targets Ambient API, not K8s API | Unlike 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 Applications | Each 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 server | The 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-in | Default 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 rules | The 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 token | Async 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 confirmation | auto_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-scoped |
Applications 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 chain |
Only 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 engine | The 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.
# 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 varkind: Credential
metadata:
name: platform-gitlab-pat
spec:
provider: gitlab
token: $GITLAB_PAT
url: https://gitlab.myco.com
labels:
team: platformacpctl apply -f credential.yaml
# credential/platform-gitlab-pat created
# Then bind to the desired project
acpctl credential bind platform-gitlab-pat --project my-projectCredentials 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).
Last updated: 2026-04-28. Use this as the authoritative index — click into component source to verify.
| Area | API Server | Go SDK | CLI (acpctl) |
Notes |
|---|---|---|---|---|
| Sessions — CRUD | ✅ | ✅ SessionAPI.{Get,List,Create,Update,Delete} |
✅ get/create/delete session |
|
| Sessions — start/stop | ✅ /start /stop |
✅ SessionAPI.{Start,Stop} |
✅ start/stop commands |
|
| Sessions — messages (list/push/watch) | ✅ /messages |
✅ PushMessage, ListMessages, WatchSessionMessages (gRPC) |
✅ session messages, session send |
gRPC watch via session_watch.go |
| Session messages (top-level) | ✅ GET /session_messages |
✅ SessionMessages().List() |
n/a | SDK/CP-internal; used by CP to resolve max seq on restart |
| Sessions — live events (SSE proxy) | ✅ /events → runner pod |
✅ SessionAPI.StreamEvents → io.ReadCloser |
✅ session events |
Runner must be Running; 502 if unreachable |
| Sessions — labels/annotations | ✅ PATCH accepts labels/annotations |
✅ fields on Session type; SessionAPI.Update(patch map[string]any) |
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/delete |
Requires 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/delete |
S3-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/tasks |
AGUI prefix routes; 502 if runner unreachable |
| Agents — CRUD | ✅ /projects/{id}/agents |
✅ ProjectAgentAPI.{ListByProject,GetByProject,GetInProject,CreateInProject,UpdateInProject,DeleteInProject} |
✅ agent list/get/create/update/delete |
|
| Agents — start/start-preview | ✅ /start |
✅ ProjectAgentAPI.{Start,GetStartPreview} |
✅ start <id>, agent start-preview |
Idempotent — returns existing session if active |
| Agents — sessions history | ✅ /sessions sub-resource |
✅ ProjectAgentAPI.Sessions |
✅ agent sessions |
Returns SessionList scoped to agent |
| Agents — labels/annotations | ✅ PATCH accepts labels/annotations |
✅ fields on ProjectAgent type; UpdateInProject(patch map[string]any) |
agent update with raw patch; no typed helpers |
|
| Inbox — list/send | ✅ GET/POST /inbox |
✅ InboxMessageAPI.{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 — CRUD | ✅ | ✅ ProjectAPI.{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) |
||
| RBAC — roles | ✅ full CRUD | ✅ RoleAPI |
✅ create role, get roles, get roles <id>, delete role |
|
| RBAC — role bindings | ✅ full CRUD | ✅ RoleBindingAPI |
✅ create role-binding, get role-bindings, get role-bindings <id>, delete role-binding |
|
| RBAC — scoped role_bindings queries | ✅ agents only; 🔲 users/projects/sessions/credentials | n/a | n/a | GET /projects/{id}/agents/{agent_id}/role_bindings implemented; other 4 scoped endpoints not yet |
| Credentials — CRUD | ✅ plugins/credentials/ (global at /credentials) |
✅ credential_api.go + credential_extensions.go |
✅ credential list/get/create/update/delete/token |
credential bind not yet implemented. |
| Credentials — token fetch | ✅ GET /credentials/{cred_id}/token |
✅ GetToken() in credential_extensions.go |
✅ credential token <id> |
Gated by credential:token-reader; granted to runner SA by operator |
| ScheduledSessions — CRUD | ✅ scheduledSessions plugin | ✅ ScheduledSessionAPI.{List,Get,Create,Update,Delete,GetByName} |
✅ scheduled-session list/get/create/update/delete |
|
| ScheduledSessions — lifecycle | ✅ suspend/resume/trigger/runs handlers | ✅ ScheduledSessionAPI.{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_URL |
n/a | 🔲 raw HTTP fallback | Permissions, keys, MCP servers, secrets, feature flags |
| Generic proxy — repo operations | ✅ proxy plugin | n/a | 🔲 raw HTTP fallback | Tree, blob, branches, seed, forks |
| Generic proxy — auth integrations | ✅ proxy plugin | n/a | n/a | GitHub/GitLab/Google/Jira/Gerrit/CodeRabbit/MCP OAuth flows |
| Generic proxy — cluster/platform | ✅ proxy plugin | n/a | 🔲 acpctl version, acpctl cluster-info |
cluster-info, version, health, LDAP, OOTB workflows |
| Declarative apply | n/a | uses SDK | ✅ apply -f, apply -k |
Upsert semantics; supports inbox seeding |
| Declarative apply — Credential kind | n/a | uses SDK | ✅ apply -f credential.yaml |
Global resource; token sourced from env var in YAML |
| Declarative apply — ScheduledSession kind | n/a | 🔲 | 🔲 | Planned; schedule and agent reference in YAML |
| Applications — CRUD | 🔲 planned | 🔲 planned | 🔲 planned | GitOps sync binding |
| Applications — sync/refresh | 🔲 planned | 🔲 planned | 🔲 planned | Trigger sync or refresh operations |
| Applications — status | 🔲 planned | 🔲 planned | 🔲 planned | Per-resource sync/health detail |
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.
| Command | Status | Path to close |
|---|---|---|
| Project/Agent/Session label subcommands | 🔲 no acpctl label/acpctl annotate |
add typed label helpers to SDK first, then CLI |
acpctl credential bind |
🔲 not implemented | POST /role_bindings with scope=credential; global migration complete, command not yet written |
| Session workspace/files/git/repos subcommands | 🔲 planned | see Session Operations table above |
Manual Test
acpctl create project --name test-cred-1 --description "cred test" acpctl project test-cred-1
acpctl agent create --project-id test-cred-1 --name github-agent
--prompt "You are a GitHub automation agent."
AGENT_ID=$(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"
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
acpctl credential bind github-pat-test --project test-cred-1
CRED_ID=$(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"
SESSION_ID=$(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"
acpctl session events "$SESSION_ID"