Feature: External Session Registration API — let terminal agents register sessions without a workspace
Summary
VK is designed for humans to create tasks and launch agents from the UI. But a significant chunk of real-world AI coding work starts differently: from a terminal claude or gemini command, a parallel worktree, or a Zora task dispatched from a message queue. None of these sessions appear in VK today — the board is stale while the work is happening.
This issue proposes a minimal API addition that would let any external agent register itself with VK so it shows up in the session list and can be tracked.
Use case
A developer runs:
cd ~/Dev/my-app
git checkout -b fix/issue-42
claudeClaude Code starts working. At this moment, VK knows nothing about this session. The board card sits in Backlog while the agent is already deep into the fix.
With this API, a global SessionStart hook (e.g. ~/.claude/settings.json) could call:
POST /api/sessions/external
{
"runtime": "claude_code",
"project_path": "/Users/ryaker/Dev/my-app",
"branch": "fix/issue-42",
"workspace_id": "optional-uuid-if-known"
}And VK would display the session alongside workspace-based sessions.
Proposed API
Endpoint
POST /api/sessions/externalRequest body
{
"workspace_id": "uuid (optional — link to VK workspace if known)",
"name": "string (optional — human-readable label)",
"runtime": "claude_code | gemini | zora | unknown",
"project_path": "/absolute/path/to/repo",
"branch": "fix/issue-42",
"pid": 12345
}Response
{
"session_id": "uuid",
"workspace_id": "uuid | null"
}Status update
PATCH /api/sessions/{session_id}
{ "status": "in_progress" | "in_review" | "done" | "blocked" }(Or reuse the existing PUT endpoint with an additional status field.)
Proposed implementation
1. Migration — add source + external fields to sessions
ALTER TABLE sessions ADD COLUMN source TEXT NOT NULL DEFAULT 'internal';
ALTER TABLE sessions ADD COLUMN external_runtime TEXT;
ALTER TABLE sessions ADD COLUMN external_project_path TEXT;
ALTER TABLE sessions ADD COLUMN external_branch TEXT;
ALTER TABLE sessions ADD COLUMN external_pid INTEGER;
ALTER TABLE sessions ADD COLUMN external_status TEXT DEFAULT 'in_progress';source: 'internal' (VK-created, workspace-required) or 'external' (registered externally).
2. Model change — workspace_id becomes nullable for external sessions
// crates/db/src/models/session.rs
pub struct Session {
pub id: Uuid,
pub workspace_id: Option<Uuid>, // was: Uuid — now nullable for external sessions
pub name: Option<String>,
pub executor: Option<String>,
pub source: String, // new: "internal" | "external"
pub external_runtime: Option<String>,
pub external_project_path: Option<String>,
pub external_branch: Option<String>,
pub external_pid: Option<i64>,
pub external_status: Option<String>,
// ...
}3. New request type
// crates/server/src/routes/sessions/mod.rs
#[derive(Debug, Deserialize, TS)]
pub struct CreateExternalSessionRequest {
pub workspace_id: Option<Uuid>,
pub name: Option<String>,
pub runtime: Option<String>,
pub project_path: Option<String>,
pub branch: Option<String>,
pub pid: Option<i64>,
}4. New handler
pub async fn create_external_session(
State(deployment): State<DeploymentImpl>,
Json(payload): Json<CreateExternalSessionRequest>,
) -> Result<ResponseJson<ApiResponse<Session>>, ApiError> {
let pool = &deployment.db().pool;
// If workspace_id provided, verify it exists
if let Some(ws_id) = payload.workspace_id {
Workspace::find_by_id(pool, ws_id)
.await?
.ok_or(ApiError::Workspace(WorkspaceError::ValidationError(
"Workspace not found".to_string(),
)))?;
}
let session = Session::create_external(pool, &CreateExternalSession {
workspace_id: payload.workspace_id,
name: payload.name,
runtime: payload.runtime,
project_path: payload.project_path,
branch: payload.branch,
pid: payload.pid,
}).await?;
Ok(ResponseJson(ApiResponse::success(session)))
}5. Route registration
// In sessions::router()
.route("/external", post(create_external_session))Frontend impact
External sessions would show in the sessions list with a visual indicator (e.g. a terminal icon vs. the VK robot icon). No launch/follow-up controls needed — external sessions are read-only from VK's perspective.
Minimal frontend change: filter source === 'external' sessions into their own section or badge them differently.
Why not a separate table?
Reusing sessions means:
- External sessions appear naturally wherever sessions are listed
- No join required for combined views
- Less schema surface area
- The
sourcefield makes the distinction clear
Prior art / motivation
I've built vk-bridge — a sidecar service that solves this today by maintaining its own session registry and mapping sessions to VK cards via the /api/remote/* endpoints. It works, but it's a workaround. A first-class external session API in VK would let vk-bridge (and any other integration) register sessions in a way VK's UI could actually display and track.
Happy to contribute a PR for this if the design direction sounds right.
Source: BloopAI/vibe-kanban