# Operator Handler System: Design Exploration (https://jackin.tailrocks.com/reference/research/operator-surface/host-affordances/operator-handler-system-design/)



**Status**: Design exploration for [Operator handler system](/roadmap/operator-handler-system/) (Phase 1, [Agent Orchestration Program](/reference/research/agent-orchestration/program-research/)) — not yet implemented

## Problem [#problem]

When an operator wants to act on something the agent produced — review a diff, open the workspace in an IDE, follow a link the agent printed — they have to context-switch to a separate shell, copy the path, run the right launcher manually. Every adjacent tool in this space (multicode, Conductor, Docker Sandboxes) ships an external-launcher abstraction precisely because this friction kills the "left it running overnight" workflow.

jackin❯ has host affordance primitives (safe URL open, verified file export, queued host open/reveal) but no named, operator-configurable handler layer. Each future feature that needs to launch an external tool would otherwise invent its own launcher.

## Why It Matters [#why-it-matters]

* The console (today) and any future TUI need to know how to "open" a path, URL, or repo without hardcoding `open` / `xdg-open`.
* Multiple downstream items in this program need it: [GitHub link tracking](/roadmap/github-link-tracking/) opens issue/PR URLs, [agent tag protocol](/roadmap/agent-tag-protocol/) opens repo paths, [custom operator tools](/roadmap/custom-operator-tools/) expand exec templates that eventually want the same launcher resolution.
* Designing the abstraction once means cross-platform handling (`open` on macOS, `xdg-open` on Linux, OS-native file URLs) lives in one place.

## Inspiration in multicode [#inspiration-in-multicode]

**Sources**:

* README — [Editor Tool](https://github.com/graemerocher/multicode#editor-tool) (covers the `[compare]` block for IDE selection)
* Config — [`config.toml` `[handler]` block](https://github.com/graemerocher/multicode/blob/main/config.toml)

```toml
[handler]
review = "/usr/bin/smerge ."          # chdir-mode launcher (no {} placeholder)
review-pty = false                     # allocate a PTY for the launcher?
web = "/usr/bin/firefox {}"            # template-mode launcher (single {} required)
```

multicode also exposes a `[compare]` block in the current reference implementation that specifies the IDE for "open in editor":

```toml
[compare]
tool = "vscode"   # or "intellij"
command = "..."   # optional override
```

The TUI keystrokes `r` (review = open diff viewer for repo) and `e` (open in IDE) call the resolved handler. Both blocks are loaded once at startup; no hot-reload.

## Recommended Shape [#recommended-shape]

One unified `[handler]` block. The `[compare]`-style IDE selector folds in as another handler kind, not a separate block.

### Config [#config]

```toml
[handler.diff]
# Diff viewer for a repo path. Invoked chdir-mode by default (cwd = arg).
command = "/opt/homebrew/bin/smerge"
mode = "chdir"

[handler.web]
# URL launcher.
command = "open {}"   # macOS default; Linux default would be `xdg-open {}`
mode = "template"

[handler.ide]
# IDE on a workspace path. Either a tool name (resolved via well-known list)
# or a full command.
tool = "vscode"
# command = "code -n -g {}"     # optional explicit override
```

Resolution rules:

* `diff` defaults to `git diff` (in-terminal) if no command is configured.
* `web` defaults to `xdg-open {}` on Linux, `open {}` on macOS, none on unsupported platforms (handler returns "no web handler configured").
* `ide.tool` accepts at least `vscode`, `intellij`, `zed`, `cursor`, plus `none` to disable. The well-known table is small and operator-extensible via `command = "..."`.
* `mode = "chdir"` means the handler runs with cwd set to the argument and no argument substitution; `mode = "template"` requires a single `{}` and substitutes verbatim.

### CLI [#cli]

```sh
jackin open <selector>           # IDE handler
jackin diff <selector>           # diff handler (in-terminal `git diff` by default)
jackin web <url>                 # web handler — explicit URL form
```

Subcommands accept the same selector grammar as `jackin load`. They resolve the workspace's primary mount path (or, if isolated, the materialized worktree path under `<data_dir>/jackin-<container>/git/worktree/repo/<dst-tree>/<container>/`) before invoking the handler.

### Console [#console]

Single keystroke per handler when a workspace row is selected: `o` (open in IDE), `d` (diff), `w` (web — only meaningful when a link is selected).

## Scope (V1) [#scope-v1]

* `[handler.diff]`, `[handler.web]`, `[handler.ide]` config blocks at the operator-config level.
* Cross-platform defaults baked in for `web` and a sensible default for `diff` (`git diff` in terminal).
* IDE well-known table covering the four named editors above.
* CLI subcommands `jackin open`, `jackin diff`, `jackin web`.
* Console keybindings for diff and IDE.
* For worktree-isolated mounts, default to opening the materialized worktree; provide a flag (CLI) and Shift-modifier (console) to open the host repo path instead.

## Defer [#defer]

* Per-workspace handler overrides. Operator-config-level only in V1; if a use case surfaces (e.g. one workspace wants its own IDE), revisit.
* In-console diff renderer. multicode uses an external viewer; jackin console gets `git diff` in a paged view, that's enough.
* PTY allocation for the diff handler (`review-pty` in multicode). Defer until someone reports an issue with a TTY-aware diff tool.

## Open Questions [#open-questions]

* Where does the operator-config file live for handlers? Options: extend the existing operator config under `[handler]`, or a new `~/.config/jackin/handlers.toml`. Recommended default: *extend operator config* — single-file ergonomics matter.
* Should `jackin diff` default to in-terminal `git diff` (current recommendation) or refuse to run without a configured handler? Recommended default: *in-terminal default*; explicit configuration is a polish, not a prerequisite.
* For worktree-isolated mounts, should `jackin open` default to the materialized worktree or the host repo? See default above; this might flip after operators try V1.
