<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/internal/config/config_test.go, branch fix/dockersandbox-gitpush-host-security</title>
<subtitle>claudomator — task automation server
</subtitle>
<id>https://git.terst.org/claudomator.git/atom?h=fix%2Fdockersandbox-gitpush-host-security</id>
<link rel='self' href='https://git.terst.org/claudomator.git/atom?h=fix%2Fdockersandbox-gitpush-host-security'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/'/>
<updated>2026-07-03T23:01:50+00:00</updated>
<entry>
<title>feat(role): add versioned role configs + escalation ladder + scheduler (Phase 5)</title>
<updated>2026-07-03T23:01:50+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T23:01:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=787b7fb1aed92c2b701724a7741576053b93cccb'/>
<id>urn:sha1:787b7fb1aed92c2b701724a7741576053b93cccb</id>
<content type='text'>
Two parts:

Part A (fixes a gap from Phase 1): Groq/OpenRouter/OpenAI were documented in
docs/api-keys-setup.md as usable once configured, but nothing actually
constructed runners for them. internal/cli/cloudrunners.go consolidates
anthropic/google/groq/openrouter/openai NativeRunner construction into one
table-driven registerCloudRunners() helper, replacing the two hand-written
per-provider blocks in serve.go/run.go. Groq/OpenRouter/OpenAI reuse
openaicompat (no new adapter code) at SandboxKind: "docker".

Part B: the token-husbanding harness's core routing mechanism.

- internal/role: RoleConfig/Tier/Rung -- a role's system prompt and a
  multi-tier (provider, model) escalation ladder, versioned via config_json.
- storage: new role_configs table (draft/active/retired, UNIQUE(role,
  version)) with transactional activate-retires-prior-active semantics;
  new executions.escalation_rung column.
- task.AgentConfig.Role string -- purely additive; every existing task shape
  (Agent.Role == "") is unaffected, proven by TestPool_Execute_NonRoleTask_Unaffected
  plus the full pre-existing suite passing unchanged.
- executor.Pool.execute(): role-typed tasks with no Agent.Type yet resolve
  tier 0 of their active ladder (round-robin across multi-candidate tiers,
  skipping rate-limited providers, falling back to soonest-clearing) before
  the existing pickAgent/Classifier path runs; SystemPrompt applies to
  Agent.SystemPromptAppend. Already-resolved role tasks (scheduler resubmits)
  get their escalation_rung re-derived read-only via findTierIndex.
- internal/scheduler: polls role-typed FAILED tasks, retries at the same
  rung under MaxRetries or escalates to the next tier's first candidate when
  budget.Accountant.Allow() permits (emitting event.KindEscalated), else
  leaves the task FAILED with a final:true KindEscalated event. An
  in-memory per-execution-ID "handled" set keeps the poll loop convergent.
  Started by `serve` only, config knob [scheduler].poll_interval_seconds.
- internal/api: POST/GET /api/roles/{role}/versions, POST
  /api/roles/{role}/activate -- unauthenticated, matching the existing
  projects/tasks REST endpoints' auth posture (only chatbot MCP, agent MCP,
  and WebSocket are api_token-gated in this codebase today).

Documented as stored-but-not-yet-enforced (CLAUDE.md Design Debt, matching
how task.Priority/RetryConfig are already documented): RoleConfig.Tools/
SandboxKind don't affect dispatch yet; DefaultBudgetUSD is read narrowly as
the scheduler's escalation cost estimate, not enforced at initial dispatch;
scheduler escalation always targets Candidates[0] (no round-robin, unlike
initial-dispatch tier-0 resolution); the scheduler's dedupe is per-process
and resets on restart (idempotent, harmless).

go build/vet/test -race -count=1 all pass, 21 packages.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>feat(provider): add native Google Gemini API adapter (Phase 4)</title>
<updated>2026-07-03T22:33:22+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T22:33:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=1f203a7ac0efad15ec3fc0a4c5b335ad7073a52f'/>
<id>urn:sha1:1f203a7ac0efad15ec3fc0a4c5b335ad7073a52f</id>
<content type='text'>
Adds internal/provider/google, the second native cloud adapter (following
internal/provider/anthropic's pattern) on top of Phase 1's provider-neutral
tool-use loop, wired to a Docker-sandboxed NativeRunner under agent.type:
"google" -- a separate execution path and budget bucket from the existing
CLI-subprocess "gemini" ContainerRunner, which is untouched.

Wire-format research (the highest-risk part of this adapter): Gemini's
multi-turn function-calling shape was resolved by cross-referencing the
REST API reference's own generateContent example against the go-genai SDK's
struct tags on GitHub -- both agree on functionCall/functionResponse parts
keyed by "name" (with an optional "id" for round-tripping ToolCall.ID),
with the response fed back inside a "user"-role Content (Gemini has no
tool/function role, mirroring Anthropic's lack of one). A separate fetched
source (the function-calling guide page) was deliberately discarded as a
reference for this shape -- it documents a different, newer "Interactions
API" whose call_id/type:"function_result" structure doesn't fit the
contents/parts/candidates shape used everywhere else.

- internal/provider/google: request/response translation, systemInstruction
  handling, role mapping (assistant-&gt;model, tool-results-&gt;user role),
  per-model-prefix pricing table (2.5 Pro/Flash/Flash-Lite, 2.0, 1.5 tiers)
- internal/retry: IsRateLimitError additively extended for RESOURCE_EXHAUSTED
- internal/config: RunnersConfig.Google/GoogleEnabled()
- internal/cli/serve.go, run.go: runners["google"] construction mirroring
  the Anthropic wiring exactly (Docker sandbox default)
- docs/api-keys-setup.md: Google marked wired-up, budget-bucket/disable/
  verify guidance added matching the Anthropic section

go build/vet/test -race all pass. No live Gemini API key available in this
environment; verified via fake-httptest-server adapter tests (plain text,
tool-use round-trip, multi-turn tool-result, rate-limit error matching) plus
a Pool/NativeRunner routing test. Live E2E is a follow-up once a key is
configured, same as Phase 2's Anthropic adapter.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>feat(provider): add native Anthropic Messages API adapter (Phase 2)</title>
<updated>2026-07-03T09:03:13+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T09:03:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=e38f673edc7428c0c836c39f40707cb681defb14'/>
<id>urn:sha1:e38f673edc7428c0c836c39f40707cb681defb14</id>
<content type='text'>
Adds internal/provider/anthropic, the first genuinely new provider.Provider
implementation on top of Phase 1's provider-neutral tool-use loop, alongside
(not replacing) the existing Docker/CLI-subprocess ContainerRunner path for
the "claude" agent type:

- internal/provider/anthropic: translates the neutral ChatRequest/ChatResponse
  shape to/from Anthropic's Messages API content-block format (system as a
  top-level field, tool_use/tool_result blocks, no "tool" role -- tool
  results become user-role messages), with a per-model-prefix pricing table
  for CostUSD
- internal/retry: IsRateLimitError additively extended to recognize
  Anthropic's rate_limit_error/overloaded_error/529 shapes
- internal/config: RunnersConfig.Anthropic/AnthropicEnabled() gate
- internal/cli/serve.go, run.go: register runners["anthropic"] as a
  NativeRunner when [providers.anthropic].api_key is set and enabled --
  tracked as a distinct executions.agent="anthropic" budget bucket, separate
  from the CLI-subprocess "claude" runner even though both bill the same
  Anthropic account

go build/vet/test -race all pass. No live Anthropic API key is available in
this environment, so verification is via fake-httptest-server adapter tests
(12 cases, incl. multi-turn tool_result round-trip and rate-limit error
matching) plus a Pool/NativeRunner routing test proving agent.type:
"anthropic" actually reaches the new provider. Live end-to-end verification
against the real API is a follow-up once a key is configured.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>refactor(executor): extract provider-neutral tool-use loop (Phase 1)</title>
<updated>2026-07-03T08:49:43+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T08:49:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=67e0081c6d573b701ed931f96e14dbe5b4258a17'/>
<id>urn:sha1:67e0081c6d573b701ed931f96e14dbe5b4258a17</id>
<content type='text'>
Splits LocalRunner's OpenAI-specific agentic loop into reusable, provider-
agnostic pieces so later phases can add native Anthropic/OpenAI/Google/Groq/
OpenRouter adapters without duplicating the control flow:

- internal/provider: neutral Provider/ChatRequest/ChatResponse types, plus
  an openaicompat adapter wrapping the existing internal/llm.Client unchanged
- internal/sandbox: Sandbox interface + HostSandbox (git clone/push/cleanup,
  read_file/write_file/run_bash/glob), lifted verbatim from local.go/localtools.go
- internal/agentloop: the extracted tool-use loop (request/response/tool-
  dispatch/loop, ask_user blocking, stream-json envelope, summary fallback)
- internal/agentchannel: AgentChannel/SubtaskSpec/BlockedError/ErrAgentBlocked
  moved out of internal/executor so agentloop can use them without an import
  cycle; internal/executor re-exports via type aliases, so no call site changes
- internal/executor/nativerunner.go: NativeRunner replaces LocalRunner,
  wiring agentloop.Loop + openaicompat + HostSandbox together
- config.Providers map[string]ProviderConfig added (unused until Phase 2+)

Zero intended behavior change: go test -race ./... passes across all
packages, and end-to-end stream-json/summary/changestats output was verified
byte-compatible against a fake OpenAI-compatible server. Adds test coverage
for sandbox tool-dispatch (git clone/push, read/write/bash/glob) that
LocalRunner never had.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>feat: add [runners] config section to toggle each runner on/off</title>
<updated>2026-05-20T19:23:45+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-05-20T19:23:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=4bef835671542d5785c7dc6229662b9d5b0053f8'/>
<id>urn:sha1:4bef835671542d5785c7dc6229662b9d5b0053f8</id>
<content type='text'>
Adds RunnersConfig{Claude,Gemini,Local,Container *bool} to Config,
parsed from a [runners] TOML section. Each field uses the *bool pointer
pattern (nil = enabled by default, false = disabled) so existing installs
require no config changes.

serve.go / run.go now gate each runner registration on the corresponding
Enabled() method. The pool's runners map remains the authoritative
routing table — absent entries are already skipped by pickAgent and
fail-fast in getRunner, so no executor changes are needed.

Example:
  [runners]
  gemini = false   # disable cloud gemini runner
  local  = true    # explicit (same as default)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(api): route elaboration through local LLM when configured</title>
<updated>2026-04-28T17:10:27+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-28T17:10:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=ae833b2765c7c8086bf8e1ea8e8ec8ee9b73e656'/>
<id>urn:sha1:ae833b2765c7c8086bf8e1ea8e8ec8ee9b73e656</id>
<content type='text'>
Phase 2 of "local OSS models as agents" plan. Adds a third elaboration
path that calls the local OpenAI-compatible LLM via the internal/llm
client, and reorders dispatch so the cheap path is tried first:
local → claude → gemini, with each next attempt only on hard failure
of the prior.

Wiring is opt-out, not opt-in: when [local_model].endpoint is set,
elaboration prefers local by default. Users with a slow or low-quality
local model can disable just elaboration via:

    [local_model]
    endpoint = "..."
    prefer_for_elaborate = false

without giving up the runner or the classifier path.

Implementation:
- Server gains an optional *llm.Client field via SetLLM (matches the
  existing SetNotifier/SetWorkspaceRoot setter pattern, no NewServer
  signature break).
- elaborateWithLocal() reuses buildElaboratePrompt verbatim and asks
  for response_format=json_object so we skip markdown-fence cleanup.
- handleElaborateTask reorders try chain; existing Claude-first
  behavior is preserved exactly when SetLLM is not called.
- LocalModel.UseForElaborate() encapsulates the default-true gating
  with a *bool so explicit-false survives TOML parse.

Tests:
- elaborateWithLocal: parses valid response, errors on nil client,
  errors on bad JSON.
- handler: local preferred when wired; falls back to claude when
  local fails; unchanged behavior when no LLM is configured.
- config: UseForElaborate gating across empty/default/explicit-true/
  explicit-false cases.

Pre-existing test failures noted in docs/plans/local-oss-runner.md
(post-epic cleanup): TestGeminiLogs_ParsedCorrectly returns 404 for
gemini execution log fetch — predates this change.

Plan: docs/plans/local-oss-runner.md.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>cli: implement --config flag to load TOML config file</title>
<updated>2026-03-10T04:36:19+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator</email>
</author>
<published>2026-03-10T04:36:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=8873187921c55d94be56364bf0b9d6b2d12127c2'/>
<id>urn:sha1:8873187921c55d94be56364bf0b9d6b2d12127c2</id>
<content type='text'>
The --config flag was registered but silently ignored. Now:
- config.LoadFile loads a TOML file on top of defaults
- PersistentPreRunE applies the file when --config is set
- Explicit CLI flags (--data-dir, --claude-bin) take precedence over the file

Tests: TestLoadFile_OverridesDefaults, TestLoadFile_MissingFile_ReturnsError,
TestRootCmd_ConfigFile_Loaded, TestRootCmd_ConfigFile_CLIFlagOverrides,
TestRootCmd_ConfigFile_Missing_ReturnsError

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>config: Default() returns error</title>
<updated>2026-03-08T20:40:47+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-08T20:40:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=db1ebb7a3f9310ca2cc483d65e9c0e578c2eb4ff'/>
<id>urn:sha1:db1ebb7a3f9310ca2cc483d65e9c0e578c2eb4ff</id>
<content type='text'>
Default() now returns (*Config, error) so callers can detect TOML parse
failures rather than silently falling back to zero values.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
