Operator Handler System: Design Exploration
Status: Design exploration for Operator handler system (Phase 1, Agent Orchestration Program) — not yet implemented
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
- 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 opens issue/PR URLs, agent tag protocol opens repo paths, custom operator tools expand exec templates that eventually want the same launcher resolution.
- Designing the abstraction once means cross-platform handling (
openon macOS,xdg-openon Linux, OS-native file URLs) lives in one place.
Inspiration in multicode
Sources:
- README — Editor Tool (covers the
[compare]block for IDE selection) - Config —
config.toml[handler]block
[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":
[compare]
tool = "vscode" # or "intellij"
command = "..." # optional overrideThe 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
One unified [handler] block. The [compare]-style IDE selector folds in as another handler kind, not a separate block.
Config
[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 overrideResolution rules:
diffdefaults togit diff(in-terminal) if no command is configured.webdefaults toxdg-open {}on Linux,open {}on macOS, none on unsupported platforms (handler returns "no web handler configured").ide.toolaccepts at leastvscode,intellij,zed,cursor, plusnoneto disable. The well-known table is small and operator-extensible viacommand = "...".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
jackin open <selector> # IDE handler
jackin diff <selector> # diff handler (in-terminal `git diff` by default)
jackin web <url> # web handler — explicit URL formSubcommands 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
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)
[handler.diff],[handler.web],[handler.ide]config blocks at the operator-config level.- Cross-platform defaults baked in for
weband a sensible default fordiff(git diffin 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
- 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 diffin a paged view, that's enough. - PTY allocation for the diff handler (
review-ptyin multicode). Defer until someone reports an issue with a TTY-aware diff tool.
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 diffdefault to in-terminalgit 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 opendefault to the materialized worktree or the host repo? See default above; this might flip after operators try V1.