Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion bindings/AgentState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,8 @@ dev_server_pid: number | null,
/**
* Path to the git worktree for this ticket (per-ticket isolation)
*/
worktree_path: string | null, };
worktree_path: string | null,
/**
* Name of the `RemoteHost` this agent's CLI runs on over SSH (None = local)
*/
remote_host: string | null, };
6 changes: 6 additions & 0 deletions bindings/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { NotificationsConfig } from "./NotificationsConfig";
import type { PathsConfig } from "./PathsConfig";
import type { QueueConfig } from "./QueueConfig";
import type { RelayConfig } from "./RelayConfig";
import type { RemoteHost } from "./RemoteHost";
import type { RestApiConfig } from "./RestApiConfig";
import type { SessionsConfig } from "./SessionsConfig";
import type { TemplatesConfig } from "./TemplatesConfig";
Expand Down Expand Up @@ -47,6 +48,11 @@ delegators: Array<Delegator>,
* Implicit builtin servers exist for each `llm_tool`'s vendor API and do not need declaration.
*/
model_servers: Array<ModelServer>,
/**
* Remote machines agents can be launched on over SSH, referenced by name
* from `DelegatorLaunchConfig.host`.
*/
hosts: Array<RemoteHost>,
/**
* Relay MCP injection configuration
*/
Expand Down
7 changes: 6 additions & 1 deletion bindings/DelegatorLaunchConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@ prompt_suffix: string | null,
/**
* Override global relay auto-inject MCP setting per-delegator (None = use global setting)
*/
operator_relay: boolean | null, };
operator_relay: boolean | null,
/**
* Name of a declared `RemoteHost` (from `Config.hosts`) to launch the agent
* CLI on over SSH. `None` = launch locally.
*/
host?: string | null, };
6 changes: 5 additions & 1 deletion bindings/DelegatorLaunchConfigDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ prompt_suffix?: string | null,
/**
* Override global relay auto-inject MCP setting per-delegator (None = use global setting)
*/
operator_relay?: boolean | null, };
operator_relay?: boolean | null,
/**
* Name of a declared `RemoteHost` to launch the agent CLI on over SSH (None = local)
*/
host?: string | null, };
9 changes: 8 additions & 1 deletion bindings/KanbanConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { GithubProjectsConfig } from "./GithubProjectsConfig";
import type { JiraConfig } from "./JiraConfig";
import type { LinearConfig } from "./LinearConfig";
import type { OpenspecConfig } from "./OpenspecConfig";

/**
* Kanban provider configuration for syncing issues from external systems
Expand All @@ -28,4 +29,10 @@ linear: { [key in string]: LinearConfig },
* branches. The two use different env vars and different scopes — see
* `docs/getting-started/kanban/github.md` for the full disambiguation.
*/
github: { [key in string]: GithubProjectsConfig }, };
github: { [key in string]: GithubProjectsConfig },
/**
* `OpenSpec` roots keyed by a free-form instance name (e.g., a repo alias).
* Experimental, pull-only: each active change under `<root_path>/changes/`
* acts as a kanban "project" whose issues are the tasks.md task groups.
*/
openspec: { [key in string]: OpenspecConfig }, };
2 changes: 1 addition & 1 deletion bindings/KanbanProviderKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
/**
* Which kanban provider an onboarding request targets.
*/
export type KanbanProviderKind = "jira" | "linear" | "github";
export type KanbanProviderKind = "jira" | "linear" | "github" | "openspec";
3 changes: 2 additions & 1 deletion bindings/ListKanbanProjectsRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type { GithubCredentials } from "./GithubCredentials";
import type { JiraCredentials } from "./JiraCredentials";
import type { KanbanProviderKind } from "./KanbanProviderKind";
import type { LinearCredentials } from "./LinearCredentials";
import type { OpenspecSourceDto } from "./OpenspecSourceDto";

/**
* Request to list projects/teams from a provider using ephemeral creds.
*/
export type ListKanbanProjectsRequest = { provider: KanbanProviderKind, jira?: JiraCredentials | null, linear?: LinearCredentials | null, github?: GithubCredentials | null, };
export type ListKanbanProjectsRequest = { provider: KanbanProviderKind, jira?: JiraCredentials | null, linear?: LinearCredentials | null, github?: GithubCredentials | null, openspec?: OpenspecSourceDto | null, };
3 changes: 2 additions & 1 deletion bindings/ListKanbanStatusesRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { GithubCredentials } from "./GithubCredentials";
import type { JiraCredentials } from "./JiraCredentials";
import type { KanbanProviderKind } from "./KanbanProviderKind";
import type { LinearCredentials } from "./LinearCredentials";
import type { OpenspecSourceDto } from "./OpenspecSourceDto";

/**
* Request to list workflow statuses/columns for a specific project using
Expand All @@ -12,4 +13,4 @@ export type ListKanbanStatusesRequest = { provider: KanbanProviderKind,
/**
* Project/team key to list statuses for
*/
project_key: string, jira?: JiraCredentials | null, linear?: LinearCredentials | null, github?: GithubCredentials | null, };
project_key: string, jira?: JiraCredentials | null, linear?: LinearCredentials | null, github?: GithubCredentials | null, openspec?: OpenspecSourceDto | null, };
21 changes: 21 additions & 0 deletions bindings/OpenspecConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* `OpenSpec` provider configuration (experimental, pull-only)
*
* The instance name is the `HashMap` key in `KanbanConfig.openspec`. There
* are no credentials — the provider reads local markdown under `root_path`.
*/
export type OpenspecConfig = {
/**
* Whether this provider is enabled
*/
enabled: boolean,
/**
* Directory containing the `OpenSpec` `changes/` tree (typically `<repo>/openspec`)
*/
root_path: string,
/**
* Operator project stamped on imported tickets (defaults to the change id)
*/
project?: string | null, };
11 changes: 11 additions & 0 deletions bindings/OpenspecSourceDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* `OpenSpec` source location supplied during onboarding. Not a credential —
* `OpenSpec` reads local markdown; there is no secret to validate or store.
*/
export type OpenspecSourceDto = {
/**
* Directory containing the `OpenSpec` `changes/` tree (e.g. "/repo/openspec")
*/
root_path: string, };
7 changes: 6 additions & 1 deletion bindings/ProjectSyncConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ type_mappings: { [key in string]: string },
* Ticket state changes (todo→doing, doing→done) and step completions with delegator info
* are reflected upstream. Default: false.
*/
bidirectional: boolean, };
bidirectional: boolean,
/**
* Operator project name stamped on tickets created from this source.
* Defaults to the external project key when unset.
*/
ticket_project?: string | null, };
27 changes: 27 additions & 0 deletions bindings/RemoteHost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* A named remote machine that agent CLI processes can be launched on over SSH.
*
* Distinct from [`ModelServer`] (where model *inference* lives) and from
* [`RemoteAgentRef`] (an export-only agent owned by another platform): a
* `RemoteHost` is where the agent *CLI process* runs. Referenced by name from
* [`DelegatorLaunchConfig::host`].
*/
export type RemoteHost = {
/**
* Unique name referenced by `DelegatorLaunchConfig.host` (e.g., "gpu-vm")
*/
name: string,
/**
* SSH destination, resolved via the user's `~/.ssh/config`
*/
ssh_alias: string,
/**
* Absolute path to the project root on the remote host
*/
workdir: string,
/**
* Optional display name for UI
*/
display_name: string | null, };
3 changes: 2 additions & 1 deletion bindings/ValidateKanbanCredentialsRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type { GithubCredentials } from "./GithubCredentials";
import type { JiraCredentials } from "./JiraCredentials";
import type { KanbanProviderKind } from "./KanbanProviderKind";
import type { LinearCredentials } from "./LinearCredentials";
import type { OpenspecSourceDto } from "./OpenspecSourceDto";

/**
* Request to validate kanban credentials without persisting them.
*/
export type ValidateKanbanCredentialsRequest = { provider: KanbanProviderKind, jira?: JiraCredentials | null, linear?: LinearCredentials | null, github?: GithubCredentials | null, };
export type ValidateKanbanCredentialsRequest = { provider: KanbanProviderKind, jira?: JiraCredentials | null, linear?: LinearCredentials | null, github?: GithubCredentials | null, openspec?: OpenspecSourceDto | null, };
3 changes: 2 additions & 1 deletion bindings/WriteKanbanConfigRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import type { KanbanProviderKind } from "./KanbanProviderKind";
import type { WriteGithubConfigBody } from "./WriteGithubConfigBody";
import type { WriteJiraConfigBody } from "./WriteJiraConfigBody";
import type { WriteLinearConfigBody } from "./WriteLinearConfigBody";
import type { WriteOpenspecConfigBody } from "./WriteOpenspecConfigBody";

/**
* Request to write or upsert a kanban config section.
*
* This endpoint does NOT take the secret — only the env var NAME
* (`api_key_env`). The secret is set via `/api/v1/kanban/session-env`.
*/
export type WriteKanbanConfigRequest = { provider: KanbanProviderKind, jira?: WriteJiraConfigBody | null, linear?: WriteLinearConfigBody | null, github?: WriteGithubConfigBody | null, };
export type WriteKanbanConfigRequest = { provider: KanbanProviderKind, jira?: WriteJiraConfigBody | null, linear?: WriteLinearConfigBody | null, github?: WriteGithubConfigBody | null, openspec?: WriteOpenspecConfigBody | null, };
18 changes: 18 additions & 0 deletions bindings/WriteOpenspecConfigBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Body for writing an `OpenSpec` instance config section.
*/
export type WriteOpenspecConfigBody = {
/**
* Instance name, used as the `[kanban.openspec.<instance>]` key
*/
instance: string,
/**
* Directory containing the `OpenSpec` `changes/` tree
*/
root_path: string,
/**
* Operator project stamped on imported tickets (optional)
*/
project?: string | null, };
24 changes: 23 additions & 1 deletion config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ connect_timeout_ms = 5000
# display_name = "Ollama (local)"
#
# # OpenRouter: one OpenAI-compatible key fronting 300+ models.
# # Set OPENROUTER_API_KEY in your environment; it is referenced, never stored.
# # Set OPENROUTER_API_KEY in the environment; it is referenced, never stored.
# [[model_servers]]
# name = "openrouter"
# kind = "openrouter"
Expand Down Expand Up @@ -159,6 +159,28 @@ connect_timeout_ms = 5000
# model = "qwen2.5-coder"
# model_server = "ollama-local"

# Remote hosts (where agent CLI processes run, over SSH)
# Distinct from model_servers (where model inference lives): a host is a
# machine Operator launches the agent CLI on, via `ssh <ssh_alias>` resolved
# through the ~/.ssh/config. Reference a host by name from a delegator's
# launch_config to run that delegator's agents remotely. The remote host needs
# tmux, the agent CLI on PATH, credentials in its own environment, and the
# project checked out at `workdir`.
#
# [[hosts]]
# name = "gpu-vm"
# ssh_alias = "gpu-vm"
# workdir = "/srv/agents/my-project"
# display_name = "GPU VM"
#
# # A delegator whose agents run on gpu-vm:
# [[delegators]]
# name = "claude-remote"
# llm_tool = "claude"
# model = "opus"
# [delegators.launch_config]
# host = "gpu-vm"

[version_check]
# Enable automatic version checking on startup
enabled = true
Expand Down
9 changes: 9 additions & 0 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ Create investigation from external alert
| `--severity` | Severity (S0, S1, S2) (default: S1) |
| `--project` | Affected project (optional) |

### `import`

Import tickets from configured kanban providers (jira, linear, github, openspec)

| Argument/Option | Description |
| --- | --- |
| `<PROVIDER>` | Provider slug (e.g. openspec). Omit to sync every configured provider |
| `<REFERENCE>` | Project/change reference (e.g. an `OpenSpec` change id, a Jira project key). Omit to sync all of the provider's configured collections |

### `create`

Create a new ticket from template
Expand Down
3 changes: 3 additions & 0 deletions docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ LLM CLI tool detection and providers
projects = []
delegators = []
model_servers = []
hosts = []

[agents]
max_parallel = 5
Expand Down Expand Up @@ -293,6 +294,8 @@ token_env = ""

[kanban.github]

[kanban.openspec]

[version_check]
enabled = true
url = "https://operator.untra.io/VERSION"
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/kanban/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Operator integrates with popular issue tracking systems to manage work items for
|----------|--------|-------|
| [Jira Cloud](/getting-started/kanban/jira/) | Supported | Full API integration |
| [Linear](/getting-started/kanban/linear/) | Supported | Full API integration |
| [GitHub Projects](/getting-started/kanban/github/) | Supported | Projects v2 GraphQL integration |
| [OpenSpec](/getting-started/kanban/openspec/) | Experimental | Local spec-driven changes; pull-only |

## How It Works

Expand Down
69 changes: 69 additions & 0 deletions docs/getting-started/kanban/openspec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: "OpenSpec"
description: "Import OpenSpec spec-driven change tasks as Operator tickets."
layout: doc
---

# OpenSpec

<span class="badge alpha">Experimental</span>

Operator can import work from [**OpenSpec**](https://github.com/Fission-AI/OpenSpec), the spec-driven development (SDD) framework for AI coding assistants. OpenSpec keeps proposed changes as plain-markdown bundles in your repository; Operator turns their task checklists into queued tickets.

> **Experimental.** This provider is pull-only and file-based. Operator never edits your OpenSpec files — checking off completed tasks in `tasks.md` remains yours (or your agent's) to do.

## How the mapping works

OpenSpec stores each proposed change at `openspec/changes/<change-id>/` with a `proposal.md`, an implementation checklist in `tasks.md`, and optional `design.md` + spec deltas. Operator maps that structure onto its kanban model:

| OpenSpec | Operator |
|----------|----------|
| Active change (`changes/<id>/`) | A kanban "project" (the change id is the project key) |
| `## 1. Group` heading in `tasks.md` | One ticket per task group |
| Checklist items under the group | The ticket's task list (embedded in the body) |
| All items checked | Group counts as `done` and is skipped on import |

Each imported ticket carries `external_provider: openspec` and `external_id: <change-id>#<group-number>` in its frontmatter, so re-running an import skips everything already in your queue — imports are idempotent.

The ticket body includes the change id, the proposal's **Why** section, the group's checklist verbatim, and a pointer to the change directory so agents read the full spec (proposal, design, deltas) before starting.

## Configuration

Add an OpenSpec root to `operator.toml`:

```toml
[kanban.openspec.myrepo]
enabled = true
root_path = "/path/to/your-repo/openspec" # the dir containing changes/
project = "yourproject" # operator project for imported tickets
```

| Setting | Default | Description |
|---------|---------|-------------|
| `enabled` | `false` | Whether this OpenSpec root is active |
| `root_path` | — | Directory containing the OpenSpec `changes/` tree |
| `project` | change id | Operator project stamped on imported tickets |

No credentials are needed — OpenSpec is local markdown.

## Importing

```bash
# Import one change's open task groups as tickets
operator import openspec add-dark-mode

# Import every active (non-archived) change under all configured roots
operator import openspec

# Sync all configured kanban providers, OpenSpec included
operator import
```

Fully-checked task groups are skipped; unchecked or partially-checked groups become `TASK` tickets (flagged `needs_issuetype_mapping` so you can retype them if desired). Re-running any of these commands only creates tickets for groups not already imported.

## Limitations

- **Pull-only.** Ticket completion is not written back to `tasks.md` checkboxes, and Operator cannot create OpenSpec changes.
- **Group granularity.** One ticket per `## N.` task group — individual checklist items are not split into their own tickets.
- **No dependency ordering.** Tickets are queued FIFO in group order; Operator's same-project sequencing keeps them from running concurrently, but there is no hard blocking between groups.
- `design.md` and spec deltas are referenced by path, not ingested.
1 change: 1 addition & 0 deletions docs/getting-started/sessions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Operator supports multiple session management backends for running AI coding age
| [cmux](/getting-started/sessions/cmux/) | Supported | macOS terminal multiplexer, manages workspaces within cmux |
| [Zellij](/getting-started/sessions/zellij/) | Supported | Terminal workspace manager, tab-per-agent model (macOS/Linux) |
| [Zed](/getting-started/sessions/zed/) | Supported | Zed editor extension; MCP context server, ACP agent, slash commands |
| [Remote Hosts (SSH)](/getting-started/sessions/remote-hosts/) | Supported | Run agent CLIs on a remote machine over SSH; dashboard stays local |

## How It Works

Expand Down
Loading
Loading