feat(agentci): add model/runner fields for multi-backend support
Tickets now carry model (sonnet/haiku/opus) and runner (claude/codex) fields. agent-runner.sh dispatches to the right backend. Defaults to claude with sonnet model for cost efficiency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d9f3b726f2
commit
eaed083f9d
3 changed files with 52 additions and 12 deletions
|
|
@ -10,10 +10,12 @@ import (
|
||||||
|
|
||||||
// AgentConfig represents a single agent machine in the config file.
|
// AgentConfig represents a single agent machine in the config file.
|
||||||
type AgentConfig struct {
|
type AgentConfig struct {
|
||||||
Host string `yaml:"host" mapstructure:"host"`
|
Host string `yaml:"host" mapstructure:"host"`
|
||||||
QueueDir string `yaml:"queue_dir" mapstructure:"queue_dir"`
|
QueueDir string `yaml:"queue_dir" mapstructure:"queue_dir"`
|
||||||
ForgejoUser string `yaml:"forgejo_user" mapstructure:"forgejo_user"`
|
ForgejoUser string `yaml:"forgejo_user" mapstructure:"forgejo_user"`
|
||||||
Active bool `yaml:"active" mapstructure:"active"`
|
Model string `yaml:"model" mapstructure:"model"` // claude model: sonnet, haiku, opus (default: sonnet)
|
||||||
|
Runner string `yaml:"runner" mapstructure:"runner"` // runner binary: claude, codex (default: claude)
|
||||||
|
Active bool `yaml:"active" mapstructure:"active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAgents reads agent targets from config and returns a map suitable for the dispatch handler.
|
// LoadAgents reads agent targets from config and returns a map suitable for the dispatch handler.
|
||||||
|
|
@ -37,9 +39,19 @@ func LoadAgents(cfg *config.Config) (map[string]handlers.AgentTarget, error) {
|
||||||
if queueDir == "" {
|
if queueDir == "" {
|
||||||
queueDir = "/home/claude/ai-work/queue"
|
queueDir = "/home/claude/ai-work/queue"
|
||||||
}
|
}
|
||||||
|
model := ac.Model
|
||||||
|
if model == "" {
|
||||||
|
model = "sonnet"
|
||||||
|
}
|
||||||
|
runner := ac.Runner
|
||||||
|
if runner == "" {
|
||||||
|
runner = "claude"
|
||||||
|
}
|
||||||
targets[name] = handlers.AgentTarget{
|
targets[name] = handlers.AgentTarget{
|
||||||
Host: ac.Host,
|
Host: ac.Host,
|
||||||
QueueDir: queueDir,
|
QueueDir: queueDir,
|
||||||
|
Model: model,
|
||||||
|
Runner: runner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,12 +61,19 @@ func LoadAgents(cfg *config.Config) (map[string]handlers.AgentTarget, error) {
|
||||||
// SaveAgent writes an agent config entry to the config file.
|
// SaveAgent writes an agent config entry to the config file.
|
||||||
func SaveAgent(cfg *config.Config, name string, ac AgentConfig) error {
|
func SaveAgent(cfg *config.Config, name string, ac AgentConfig) error {
|
||||||
key := fmt.Sprintf("agentci.agents.%s", name)
|
key := fmt.Sprintf("agentci.agents.%s", name)
|
||||||
return cfg.Set(key, map[string]any{
|
data := map[string]any{
|
||||||
"host": ac.Host,
|
"host": ac.Host,
|
||||||
"queue_dir": ac.QueueDir,
|
"queue_dir": ac.QueueDir,
|
||||||
"forgejo_user": ac.ForgejoUser,
|
"forgejo_user": ac.ForgejoUser,
|
||||||
"active": ac.Active,
|
"active": ac.Active,
|
||||||
})
|
}
|
||||||
|
if ac.Model != "" {
|
||||||
|
data["model"] = ac.Model
|
||||||
|
}
|
||||||
|
if ac.Runner != "" {
|
||||||
|
data["runner"] = ac.Runner
|
||||||
|
}
|
||||||
|
return cfg.Set(key, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAgent removes an agent from the config file.
|
// RemoveAgent removes an agent from the config file.
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import (
|
||||||
type AgentTarget struct {
|
type AgentTarget struct {
|
||||||
Host string // SSH destination (e.g., "claude@192.168.0.201")
|
Host string // SSH destination (e.g., "claude@192.168.0.201")
|
||||||
QueueDir string // Remote queue directory (e.g., "~/ai-work/queue")
|
QueueDir string // Remote queue directory (e.g., "~/ai-work/queue")
|
||||||
|
Model string // AI model: sonnet, haiku, opus (default: sonnet)
|
||||||
|
Runner string // Runner binary: claude, codex (default: claude)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchTicket is the JSON payload written to the agent's queue.
|
// DispatchTicket is the JSON payload written to the agent's queue.
|
||||||
|
|
@ -33,6 +35,8 @@ type DispatchTicket struct {
|
||||||
ForgeURL string `json:"forge_url"`
|
ForgeURL string `json:"forge_url"`
|
||||||
ForgeToken string `json:"forge_token"`
|
ForgeToken string `json:"forge_token"`
|
||||||
ForgeUser string `json:"forgejo_user"`
|
ForgeUser string `json:"forgejo_user"`
|
||||||
|
Model string `json:"model,omitempty"`
|
||||||
|
Runner string `json:"runner,omitempty"`
|
||||||
CreatedAt string `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +97,8 @@ func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.Pipelin
|
||||||
ForgeURL: h.forgeURL,
|
ForgeURL: h.forgeURL,
|
||||||
ForgeToken: h.token,
|
ForgeToken: h.token,
|
||||||
ForgeUser: signal.Assignee,
|
ForgeUser: signal.Assignee,
|
||||||
|
Model: agent.Model,
|
||||||
|
Runner: agent.Runner,
|
||||||
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,15 +101,30 @@ The repo is cloned at the current directory on branch '${TARGET_BRANCH}'.
|
||||||
Create a feature branch from '${TARGET_BRANCH}', make minimal targeted changes, commit referencing #${ISSUE_NUM}, and push.
|
Create a feature branch from '${TARGET_BRANCH}', make minimal targeted changes, commit referencing #${ISSUE_NUM}, and push.
|
||||||
Then create a PR targeting '${TARGET_BRANCH}' using the forgejo MCP tools or git push."
|
Then create a PR targeting '${TARGET_BRANCH}' using the forgejo MCP tools or git push."
|
||||||
|
|
||||||
# --- 9. Run Claude ---
|
# --- 9. Run AI agent ---
|
||||||
|
MODEL=$(jq -r '.model // "sonnet"' "$TICKET_FILE")
|
||||||
|
RUNNER=$(jq -r '.runner // "claude"' "$TICKET_FILE")
|
||||||
LOG_FILE="$LOG_DIR/${REPO_OWNER}-${REPO_NAME}-${ISSUE_NUM}.log"
|
LOG_FILE="$LOG_DIR/${REPO_OWNER}-${REPO_NAME}-${ISSUE_NUM}.log"
|
||||||
echo "$(date -Iseconds) Running claude..."
|
|
||||||
echo "$PROMPT" | claude -p \
|
echo "$(date -Iseconds) Running ${RUNNER} (model: ${MODEL})..."
|
||||||
--dangerously-skip-permissions \
|
|
||||||
--output-format text \
|
case "$RUNNER" in
|
||||||
> "$LOG_FILE" 2>&1
|
codex)
|
||||||
|
codex --approval-mode full-auto \
|
||||||
|
--quiet \
|
||||||
|
"$PROMPT" \
|
||||||
|
> "$LOG_FILE" 2>&1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "$PROMPT" | claude -p \
|
||||||
|
--model "$MODEL" \
|
||||||
|
--dangerously-skip-permissions \
|
||||||
|
--output-format text \
|
||||||
|
> "$LOG_FILE" 2>&1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
EXIT_CODE=$?
|
EXIT_CODE=$?
|
||||||
echo "$(date -Iseconds) Claude exited with code: $EXIT_CODE"
|
echo "$(date -Iseconds) ${RUNNER} exited with code: $EXIT_CODE"
|
||||||
|
|
||||||
# --- 10. Move to done ---
|
# --- 10. Move to done ---
|
||||||
mv "$TICKET_FILE" "$DONE_DIR/"
|
mv "$TICKET_FILE" "$DONE_DIR/"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue