Skip to content

feat: add DevOps-Agent for operational and system administration tasks#41

Merged
iohub merged 4 commits into
mainfrom
feat-openai-go
May 6, 2026
Merged

feat: add DevOps-Agent for operational and system administration tasks#41
iohub merged 4 commits into
mainfrom
feat-openai-go

Conversation

@iohub
Copy link
Copy Markdown
Owner

@iohub iohub commented May 6, 2026

Summary by Sourcery

Introduce a DevOps-focused operational agent and a TUI command mode optimized for long-running tasks, while updating configuration, prompts, and documentation to reflect the expanded multi-agent capabilities.

New Features:

  • Add DevOps-Agent with dedicated prompt, tool set, and conductor delegation for operational and system administration tasks.
  • Introduce TUI command mode that auto-enables during running tasks, hides input, and provides streamlined keyboard controls for navigation and cancellation.

Enhancements:

  • Extend Conductor and initialization wiring to construct and use DevOps-Agent, including configurable max steps and disable flags.
  • Update Chat-Agent and agent capabilities in conductor prompt and language resources to better route tasks across agents.

Documentation:

  • Expand English and Chinese READMEs with DevOps-Agent documentation, configuration options, and updated agent/tool capability tables.
  • Update CLI usage text to reflect support for disabling the DevOps agent in addition to existing agents.

Tests:

  • Adjust Conductor tests to accommodate the new DevOps-Agent dependency while preserving existing behaviors.

iohub added 4 commits May 6, 2026 10:19
- Auto-enable command mode after task submission with minimal UI
- Hide input textarea and show compact command prompt with tips
- Add keyboard shortcuts: f/b for page up/down, j/k for scroll,
  i/enter to exit, esc to cancel running task
- Auto-disable command mode when task completes
- Add i18n translations for command mode prompt and tips (zh/en)
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 6, 2026

Reviewer's Guide

Adds a new DevOps-Agent for operational/system administration tasks, wires it into the conductor and app configuration, updates prompts, i18n, CLI/docs, and introduces a TUI command mode for streamlined interaction while tasks are running.

Sequence diagram for Conductor delegating an operational task to DevOps-Agent

sequenceDiagram
    actor User
    participant TUI as TUI_model
    participant CodingAssistant
    participant Conductor as ConductorAgent
    participant DevOps as DevOpsAgent
    participant SysOps as SysOps_RunBash

    User->>TUI: Enter operational request
    TUI->>CodingAssistant: ProcessConversation(TaskRequest)
    CodingAssistant->>Conductor: Run task via LLM loop

    Note over Conductor: Classifies task as DevOps

    Conductor->>Conductor: Call delegate_devops tool
    Conductor->>DevOps: Run(ctx, task)
    DevOps->>DevOps: Build ExecutorConfig with devopsPrompt and Adapters

    DevOps->>SysOps: ExecuteRunBash(ctx, params) via run_bash adapter
    SysOps-->>DevOps: Command output

    DevOps-->>Conductor: Final DevOps result
    Conductor-->>CodingAssistant: Aggregated response
    CodingAssistant-->>TUI: Response text
    TUI-->>User: Rendered result in viewport
Loading

File-Level Changes

Change Details Files
Introduce DevOps-Agent implementation and integrate it into the conductor orchestration pipeline.
  • Add DevOpsAgent type with its own prompt, tool adapters for bash, filesystem, search, thinking, micro_agent, and agent_exit, and guarded tool usage
  • Extend ConductorAgent to hold a DevOpsAgent reference and expose a delegate_devops tool that routes operational tasks to DevOps-Agent
  • Update conductor prompt to describe DevOps-Agent, its decision tree placement, and its role as a working agent alongside Coding- and Chat-Agent
  • Adjust conductor tests and constructor invocations to account for the new DevOpsAgent parameter
internal/agents/devops.go
internal/agents/devops.prompt.md
internal/agents/conductor.go
internal/agents/conductor.prompt.md
internal/agents/conductor_test.go
Wire DevOps-Agent into application initialization, configuration, and CLI disablement controls.
  • Add DevOpsMaxSteps to AgentConfig and thread it through CodingAssistant.Init with a default of 15 steps
  • Instantiate DevOpsAgent during initialization and pass it into NewConductorAgent
  • Extend disabled agent parsing and CLI usage/help text to recognize the devops agent flag
internal/app/app.go
internal/config/config.go
main.go
Document DevOps-Agent capabilities and update multi-agent architecture descriptions in both English and Chinese READMEs (plus architecture diagram).
  • Update agent lists, tool tables, and max-step examples to include DevOps-Agent and its tools
  • Describe DevOps-Agent role, capabilities, example use cases, configuration, and disable flag in dedicated sections
  • Adjust ChatAgent tool description to reflect use of micro_agent, thinking, and finish instead of being tool-less
  • Update architecture SVG to reflect the new DevOps-Agent (contents not diffed but implied by file change)
README.md
README_zh.md
docs/architecture.svg
Add TUI command mode for running-task interaction with hidden input and minimal navigation/cancel controls.
  • Extend TUI model with a commandMode flag that is enabled when a task or follow-up is submitted and reset on task completion
  • Implement command mode key handling which, while a task is running, focuses on quitting, cancelling, exiting command mode, and viewport navigation, delegating other keys to the viewport
  • Modify View rendering so that when in command mode, the input textarea is hidden, a localized COMMAND/命令模式 prompt and tips are shown, and the standard footer status line is suppressed while a task is running
tui.go
Extend i18n to support command mode UI text and fix minor formatting.
  • Add CommandModePrompt and CommandModeTips fields to translations and implement language-specific strings for English and Chinese
  • Update LanguageManager.GetText to return the new command mode strings and keep a proper default fallback
  • Normalize ConfirmDialogHelp string formatting for English
i18n.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@iohub iohub merged commit b65d168 into main May 6, 2026
1 check passed
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In NewConductorAgent, delegateDevOps is created unconditionally and calls devops.Run, but several tests pass nil for the DevOpsAgent and disabledAgents is nil, so using delegate_devops would panic; consider only constructing/adding this adapter when devops != nil and the agent isn't disabled.
  • When cancelling a running task in command mode via esc in Update, commandMode remains true even though the task is no longer running; you may want to explicitly reset commandMode to false there to keep the state consistent and avoid confusing UI behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `NewConductorAgent`, `delegateDevOps` is created unconditionally and calls `devops.Run`, but several tests pass `nil` for the DevOpsAgent and `disabledAgents` is `nil`, so using `delegate_devops` would panic; consider only constructing/adding this adapter when `devops != nil` and the agent isn't disabled.
- When cancelling a running task in command mode via `esc` in `Update`, `commandMode` remains true even though the task is no longer running; you may want to explicitly reset `commandMode` to false there to keep the state consistent and avoid confusing UI behavior.

## Individual Comments

### Comment 1
<location path="internal/agents/conductor_test.go" line_range="54" />
<code_context>
 	t.Helper()
 	gctx := newTestGlobalCtx(workDir)
 	engine := &mockEngine{}
-	return NewConductorAgent(gctx, engine, nil, nil, nil, nil, 10, nil, 3)
+	return NewConductorAgent(gctx, engine, nil, nil, nil, nil, nil, 10, nil, 3)
 }

</code_context>
<issue_to_address>
**suggestion (testing):** Add tests to cover the new DevOps delegate wiring and disabledAgents behaviour

The Conductor now wires a DevOpsAgent and registers a `delegate_devops` adapter, controlled by `disabledAgents["devops"]`, but `conductor_test.go` doesn’t verify this. Please add focused tests that:

1. Build a Conductor with a non-nil DevOpsAgent and `disabledAgents` unset or not containing `"devops"`, assert an adapter with `Name() == "delegate_devops"` is registered, and that invoking it calls `DevOpsAgent.Run`.
2. Build a Conductor with `disabledAgents["devops"] = true` and assert no `delegate_devops` adapter is registered.

This will ensure the new agent is correctly wired and respects the disable configuration.

Suggested implementation:

```golang
	t.Helper()
	gctx := newTestGlobalCtx(workDir)
	engine := &mockEngine{}
	return NewConductorAgent(gctx, engine, nil, nil, nil, nil, nil, 10, nil, 3)
}

type mockDevOpsAgent struct {
	runCount int32
}

func (m *mockDevOpsAgent) Run(ctx context.Context) error {
	atomic.AddInt32(&m.runCount, 1)
	return nil
}

func TestConductorRegistersDevOpsDelegateWhenEnabled(t *testing.T) {
	t.Helper()

	workDir := t.TempDir()
	gctx := newTestGlobalCtx(workDir)
	engine := &mockEngine{}
	devOpsAgent := &mockDevOpsAgent{}
	disabledAgents := map[string]bool{}

	conductor := NewConductorAgent(
		gctx,
		engine,
		nil, // planner
		nil, // coder
		nil, // executor
		nil, // tools
		nil, // git
		devOpsAgent,
		10,              // maxDepth or similar
		disabledAgents,  // disabledAgents
		3,               // parallelism or similar
	)

	var devOpsAdapterFound bool
	for _, a := range conductor.adapters {
		if a.Name() == "delegate_devops" {
			devOpsAdapterFound = true

			if r, ok := a.(interface {
				Run(ctx context.Context) error
			}); ok {
				if err := r.Run(context.Background()); err != nil {
					t.Fatalf("delegate_devops adapter Run() returned error: %v", err)
				}
				if got := atomic.LoadInt32(&devOpsAgent.runCount); got != 1 {
					t.Fatalf("expected DevOpsAgent.Run to be called once, got %d", got)
				}
			}
			break
		}
	}

	if !devOpsAdapterFound {
		t.Fatalf("expected delegate_devops adapter to be registered when devops agent is enabled")
	}
}

func TestConductorDoesNotRegisterDevOpsDelegateWhenDisabled(t *testing.T) {
	t.Helper()

	workDir := t.TempDir()
	gctx := newTestGlobalCtx(workDir)
	engine := &mockEngine{}
	devOpsAgent := &mockDevOpsAgent{}
	disabledAgents := map[string]bool{
		"devops": true,
	}

	conductor := NewConductorAgent(
		gctx,
		engine,
		nil, // planner
		nil, // coder
		nil, // executor
		nil, // tools
		nil, // git
		devOpsAgent,
		10,
		disabledAgents,
		3,
	)

	for _, a := range conductor.adapters {
		if a.Name() == "delegate_devops" {
			t.Fatalf("did not expect delegate_devops adapter to be registered when disabledAgents[\"devops\"] is true")
		}
	}
}

// makeMetaOutput builds a valid Meta-Agent JSON output string.

```

1. Ensure `internal/agents/conductor_test.go` already imports `context` and `sync/atomic`. If not, add them to the import block:
   - `import ("context" "sync/atomic" ...)`.
2. Adjust the `mockDevOpsAgent.Run` signature to match the real `DevOpsAgent` interface (e.g. `Run(ctx context.Context, gctx *GlobalContext) error`) and update the test calls accordingly.
3. Update the argument positions in the `NewConductorAgent` calls if the parameter ordering differs:
   - The code above assumes the new `DevOpsAgent` parameter is the argument immediately before `10`, and that the map argument immediately before the final `3` is `disabledAgents`.
4. If `ConductorAgent` does not expose its adapters via a public `adapters` field, replace `conductor.adapters` with the correct accessor (e.g. `conductor.Adapters()`), and adjust the loop type if adapters are stored as pointers or interfaces with a different name.
5. If the adapter interface for `delegate_devops` has a different `Run` method signature, update the type assertion and invocation in `TestConductorRegistersDevOpsDelegateWhenEnabled` to use that signature, while still asserting that the mock’s `runCount` increments when the adapter is invoked.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

t.Helper()
gctx := newTestGlobalCtx(workDir)
engine := &mockEngine{}
return NewConductorAgent(gctx, engine, nil, nil, nil, nil, 10, nil, 3)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add tests to cover the new DevOps delegate wiring and disabledAgents behaviour

The Conductor now wires a DevOpsAgent and registers a delegate_devops adapter, controlled by disabledAgents["devops"], but conductor_test.go doesn’t verify this. Please add focused tests that:

  1. Build a Conductor with a non-nil DevOpsAgent and disabledAgents unset or not containing "devops", assert an adapter with Name() == "delegate_devops" is registered, and that invoking it calls DevOpsAgent.Run.
  2. Build a Conductor with disabledAgents["devops"] = true and assert no delegate_devops adapter is registered.

This will ensure the new agent is correctly wired and respects the disable configuration.

Suggested implementation:

	t.Helper()
	gctx := newTestGlobalCtx(workDir)
	engine := &mockEngine{}
	return NewConductorAgent(gctx, engine, nil, nil, nil, nil, nil, 10, nil, 3)
}

type mockDevOpsAgent struct {
	runCount int32
}

func (m *mockDevOpsAgent) Run(ctx context.Context) error {
	atomic.AddInt32(&m.runCount, 1)
	return nil
}

func TestConductorRegistersDevOpsDelegateWhenEnabled(t *testing.T) {
	t.Helper()

	workDir := t.TempDir()
	gctx := newTestGlobalCtx(workDir)
	engine := &mockEngine{}
	devOpsAgent := &mockDevOpsAgent{}
	disabledAgents := map[string]bool{}

	conductor := NewConductorAgent(
		gctx,
		engine,
		nil, // planner
		nil, // coder
		nil, // executor
		nil, // tools
		nil, // git
		devOpsAgent,
		10,              // maxDepth or similar
		disabledAgents,  // disabledAgents
		3,               // parallelism or similar
	)

	var devOpsAdapterFound bool
	for _, a := range conductor.adapters {
		if a.Name() == "delegate_devops" {
			devOpsAdapterFound = true

			if r, ok := a.(interface {
				Run(ctx context.Context) error
			}); ok {
				if err := r.Run(context.Background()); err != nil {
					t.Fatalf("delegate_devops adapter Run() returned error: %v", err)
				}
				if got := atomic.LoadInt32(&devOpsAgent.runCount); got != 1 {
					t.Fatalf("expected DevOpsAgent.Run to be called once, got %d", got)
				}
			}
			break
		}
	}

	if !devOpsAdapterFound {
		t.Fatalf("expected delegate_devops adapter to be registered when devops agent is enabled")
	}
}

func TestConductorDoesNotRegisterDevOpsDelegateWhenDisabled(t *testing.T) {
	t.Helper()

	workDir := t.TempDir()
	gctx := newTestGlobalCtx(workDir)
	engine := &mockEngine{}
	devOpsAgent := &mockDevOpsAgent{}
	disabledAgents := map[string]bool{
		"devops": true,
	}

	conductor := NewConductorAgent(
		gctx,
		engine,
		nil, // planner
		nil, // coder
		nil, // executor
		nil, // tools
		nil, // git
		devOpsAgent,
		10,
		disabledAgents,
		3,
	)

	for _, a := range conductor.adapters {
		if a.Name() == "delegate_devops" {
			t.Fatalf("did not expect delegate_devops adapter to be registered when disabledAgents[\"devops\"] is true")
		}
	}
}

// makeMetaOutput builds a valid Meta-Agent JSON output string.
  1. Ensure internal/agents/conductor_test.go already imports context and sync/atomic. If not, add them to the import block:
    • import ("context" "sync/atomic" ...).
  2. Adjust the mockDevOpsAgent.Run signature to match the real DevOpsAgent interface (e.g. Run(ctx context.Context, gctx *GlobalContext) error) and update the test calls accordingly.
  3. Update the argument positions in the NewConductorAgent calls if the parameter ordering differs:
    • The code above assumes the new DevOpsAgent parameter is the argument immediately before 10, and that the map argument immediately before the final 3 is disabledAgents.
  4. If ConductorAgent does not expose its adapters via a public adapters field, replace conductor.adapters with the correct accessor (e.g. conductor.Adapters()), and adjust the loop type if adapters are stored as pointers or interfaces with a different name.
  5. If the adapter interface for delegate_devops has a different Run method signature, update the type assertion and invocation in TestConductorRegistersDevOpsDelegateWhenEnabled to use that signature, while still asserting that the mock’s runCount increments when the adapter is invoked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant