<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/docs, 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>docs: update api-keys-setup.md for Phase 2 (Anthropic now wired up)</title>
<updated>2026-07-03T09:04:06+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T09:04:06+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=81ae7d598e7ec6afe8afb5bbdbfe807042298559'/>
<id>urn:sha1:81ae7d598e7ec6afe8afb5bbdbfe807042298559</id>
<content type='text'>
Marks Anthropic as live (agent.type: "anthropic" works once a key is
configured), adds the budget-bucket-separation and [runners] disable notes
that Phase 2's implementation agent independently drafted in a since-discarded
duplicate doc, adds an explicit warning against using claude setup-token /
CLI OAuth session for the native adapter (subscription auth is for Claude
Code/Agent SDK use, not a separate product's API calls, per Anthropic's Agent
SDK terms), and bumps default_model examples from claude-sonnet-4-5 to
claude-sonnet-5.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>docs: add API key setup guide for Groq, OpenRouter, OpenAI, Google, Anthropic</title>
<updated>2026-07-03T09:02:35+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T09:02:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=bbd58e8d77f7c996add29b69f600e835a922542e'/>
<id>urn:sha1:bbd58e8d77f7c996add29b69f600e835a922542e</id>
<content type='text'>
Covers signup flow, current free-tier limits, and where credentials land in
config.toml for every provider the harness redesign targets. Written earlier
but never committed, so it was invisible to the Phase 1/2 implementation
worktrees — landing it now before Phase 2 merges to avoid two divergent
versions of this file.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>docs: add OSS replacement redesign plan</title>
<updated>2026-05-22T08:53:53+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-22T08:53:53+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=c24fdaa9b6bce49f5c0a1c589fd064d46b62b88d'/>
<id>urn:sha1:c24fdaa9b6bce49f5c0a1c589fd064d46b62b88d</id>
<content type='text'>
Captures the locked architectural decisions from the chatbot-driven
redesign discussion: SQLite as source of truth with external trackers
as input feeds, events table replacing ad-hoc task columns, per-runner
structured agent channel, three-runner roster with classifier, scoped
auth, multi-dimensional budget, per-runner sovereign resume.

https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
</content>
</entry>
<entry>
<title>merge: integrate github/main — LocalRunner, real GeminiRunner, llm client</title>
<updated>2026-05-13T04:02:20+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-05-13T04:02:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=68399a598924775a3ec22a39c2336ae497fb07f3'/>
<id>urn:sha1:68399a598924775a3ec22a39c2336ae497fb07f3</id>
<content type='text'>
Merges 12 commits from github/main (formerly master) that were developed
independently. Key additions:
- LocalRunner: OpenAI-compatible local LLM execution (Ollama, LM Studio)
- Real GeminiRunner with full sandbox parity to ClaudeRunner
- llm.Client for enriching CI failures and elaboration via local model
- retry.ParseRetryAfter moved to shared package
- tokens_in/tokens_out columns in executions table

Conflict resolutions:
- Kept local main's VAPID/push, stories, projects, agent events schema
- Merged both sets of Config fields (local + LocalModel from github/main)
- Unified activePerAgent accounting (decActiveAgent helper)
- Removed duplicate helpers from claude.go (now in helpers.go)
- Fixed double-decrement bug in handleRunResult vs decActiveAgent

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: rewrite CLAUDE.md with accurate architecture, known bugs, and design debt; add improvement plan</title>
<updated>2026-05-03T17:58:37+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-06T06:52:37+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=eedff0c86363f3bb1d88871bb6c21ee283cc356b'/>
<id>urn:sha1:eedff0c86363f3bb1d88871bb6c21ee283cc356b</id>
<content type='text'>
- Fix YAML key in Task format (agent: not claude:)
- Document complete state machine including BLOCKED and READY states
- Document sandbox lifecycle and git clone failure root cause (sandboxDir shadowing)
- Document all packages including notify, deployment, version, classifier, preamble
- Call out GeminiRunner as a non-functional stub
- Document design debt: unused priority/retry fields, double changestats extraction,
  hardcoded master branch, context.Background() in resume, non-atomic exec creation
- Add docs/IMPROVEMENT_PLAN.md with 20 prioritized issues ranked by effort and impact

https://claude.ai/code/session_01W8P6wwDYF7dpETXWVXCz5L
</content>
</entry>
<entry>
<title>chore: close deferred work — real GeminiRunner, Local UI option, db.go cleanup</title>
<updated>2026-05-03T08:00:20+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-03T08:00:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=e7b382bf177cbe518af3d86c3ee6c49344d225f4'/>
<id>urn:sha1:e7b382bf177cbe518af3d86c3ee6c49344d225f4</id>
<content type='text'>
Closes the three items left on the deferred queue after the post-epic
cleanup.

GeminiRunner.execOnce now actually executes the gemini binary instead
of writing hardcoded stream data. Mirrors ClaudeRunner.execOnce:
- exec.CommandContext with the same env vars (CLAUDOMATOR_API_URL etc.)
- process group SIGKILL on context cancel
- stdout piped through parseGeminiStream → stdoutFile
- stderr to file
- exit codes captured, stderr tail surfaced on failure

Test infrastructure bug uncovered in passing: testServerWithGeminiMockRunner's
mock script used double-quoted echo with literal triple-backticks, which
bash interpreted as command substitution. The script always produced
empty output. The bug was invisible until now because GeminiRunner
ignored the script entirely. Switched to a single-quoted heredoc.

Frontend: index.html dropdown gains a "Local" option. No JS branching
needed — the value flows through to agent.type verbatim and downstream
display reads the type string as-is.

storage/db.go: removed stale debug-comment scaffolding (the "TODO:
Replace with proper logger" block) that was tracking a dead
`fmt.Printf` call. The path it commented on is fine without logging —
unmarshal errors are returned wrapped.

Test status: `go test -race ./...` green across every package, zero
skips, zero excluded tests.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>chore: post-epic cleanup — green test suite, no skips</title>
<updated>2026-05-03T03:58:19+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-03T03:58:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=85c3bf4d28b0903a2005356339e6ea56855b8c80'/>
<id>urn:sha1:85c3bf4d28b0903a2005356339e6ea56855b8c80</id>
<content type='text'>
Addresses the cleanup queue captured in docs/plans/local-oss-runner.md
after the local-OSS-models epic landed. After this commit
`go test -race ./...` is green across every package with zero `t.Skip`
calls and no excluded tests.

Real bugs fixed:
- claude.go setupSandbox callsites used `sandboxDir, err := ...` which
  shadowed the outer variable, so BlockedError.SandboxDir was always
  empty. Resume-after-block was broken for both new and stale-sandbox
  paths. TestBlockedError_IncludesSandboxDir now exercises the right
  invariant.
- TestPool_ActivePerAgent_DeletesZeroEntries flake under -race: the
  cleanup defer in execute()/executeResume() runs AFTER
  handleRunResult sends on resultCh, so consumers observing a result
  could see a still-counted activePerAgent entry. Extracted
  decActiveAgent(agentType, *cleaned) helper; called explicitly before
  every resultCh send, defer becomes a no-op via the cleaned flag.
  Verified clean over `go test -race -count=10`.

Test infrastructure made hermetic:
- gitSafe now also passes -c commit.gpgsign=false / -c tag.gpgsign=false
  so sandbox tests pass on hosts whose global config requires signing.
- Bare repos in tests initialized with `-b main` (HEAD symbolic ref
  matched to the branch we push) so `git log` after push works.
- TestSandboxCloneSource_FallsBackToOrigin uses a local-FS origin URL,
  matching sandboxCloneSource's intentional filter against network URLs.
- TestGeminiLogs_ParsedCorrectly URL fixed to the actual log route
  (/api/executions/{id}/log).

GeminiRunner gap closed (partial):
- parseGeminiStream now walks lines for `result` events, surfacing
  is_error as an error and total_cost_usd as the float return value.
- GeminiRunner.Run propagates parsed cost to Execution.CostUSD.
- TestParseGeminiStream_ParsesStructuredOutput unskipped.

Notes:
- GeminiRunner is still simulated end-to-end (Run writes hardcoded
  stream data instead of execing the binary). The result/cost parser
  now exists; finishing the runner is a smaller, contained follow-up.
  Kept on the deferred queue.
- Frontend "Local" agent option and a minor storage.db.go logger TODO
  remain on the deferred queue, both intentionally — neither blocks
  anything in flight.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>feat(executor): synthesize execution summary via local LLM fallback</title>
<updated>2026-05-02T08:00:17+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-02T08:00:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=50f8fe8c1ff8b82e0bd399e5776e58bda3e57d1c'/>
<id>urn:sha1:50f8fe8c1ff8b82e0bd399e5776e58bda3e57d1c</id>
<content type='text'>
Phase 4 of "local OSS models as agents" plan. Closes the epic.

When an execution finishes and the agent did NOT write a "## Summary"
heading in its stdout (so the existing extractSummary path returns
empty), and the Pool has a local LLM configured, we now synthesize a
2-4 sentence summary from the assistant text content of the log tail.

Behavior:
- Primary path unchanged: if the agent wrote "## Summary", that wins
  byte-for-byte (TestPool_HandleRunResult_ExtractSummaryWins guards).
- Fallback path: empty extractSummary + Pool.LLM != nil → synthesize.
- All-empty path: when no LLM is configured, summary stays empty —
  identical to pre-Phase-4 behavior.

Implementation:
- Pool gains an LLM *llm.Client field, wired in serve.go and run.go
  alongside Classifier.LLM (same localClient used everywhere).
- New synthesizeSummary in internal/executor/summary.go:
  * 6s timeout so a slow local model can't stall finalization
  * 16 KB tail cap on the stdout log
  * readAssistantTextTail seeks to the last 16 KB and skips the
    first (likely partial) line, parses each line as a stream-json
    event, joins assistant `text` blocks (skips system/result/etc).
  * Returns "" on any error so the caller's behavior never regresses.
- handleRunResult: 3-tier summary resolution — exec.Summary set by
  runner → extractSummary → synthesizeSummary → empty.
- minimalMockStore now records UpdateTaskSummary calls (additive;
  existing tests unaffected) so integration tests can assert.

Tests (9 new):
- synthesizeSummary nil client / empty path / missing file all
  return "" without HTTP calls.
- empty assistant content short-circuits without LLM call.
- success path returns trimmed body, with both assistant texts in
  the user prompt.
- LLM 500 returns "" (caller handles same as no-summary).
- readAssistantTextTail seeks past early content in a large file.
- Pool integration: ## Summary present → LLM not called, agent text
  used. ## Summary absent + LLM set → LLM called, synthesized summary
  recorded against the right task ID.

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

Epic complete. Post-epic deep cleanup queue captured in the same plan
file for follow-up.

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
</feed>
