<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/docs, branch main</title>
<subtitle>claudomator — task automation server
</subtitle>
<id>https://git.terst.org/claudomator.git/atom?h=main</id>
<link rel='self' href='https://git.terst.org/claudomator.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/'/>
<updated>2026-05-13T04:02:20+00:00</updated>
<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>
<entry>
<title>feat(api): enrich CI failure task instructions via local LLM</title>
<updated>2026-05-02T07:54:51+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-02T07:54:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=6c5762848f4f3114a6ece9ce0bc70a84fca040ce'/>
<id>urn:sha1:6c5762848f4f3114a6ece9ce0bc70a84fca040ce</id>
<content type='text'>
Phase 3 of "local OSS models as agents" plan. When the webhook handler
creates a task for a failed CI run AND a local LLM is configured on
the server, the hardcoded 4-step investigation template is replaced
with a project-aware investigation plan generated by the LLM.

Scope adjustment from the original sketch: the original plan said
"summarize fetched workflow logs", but fetching logs requires GitHub
API auth that isn't wired. Narrowed to project-context triage —
recent git log + CLAUDE.md content + webhook metadata, fed to the
LLM with a system prompt asking for 6-12 lines of concrete next
steps. Deferred GitHub log fetching to post-epic cleanup.

Implementation:
- New internal/api/webhook_llm.go holds enrichCIInstructions and its
  helpers (readRecentCommits via `git log`, readProjectDoc).
- enrichCIInstructions is truly additive: any failure mode (no client,
  HTTP error, empty body, 10s timeout) returns the original fallback
  template unchanged. Existing webhook tests pass byte-for-byte.
- Always preserves a metadata header (repo/branch/SHA/check/URL)
  ahead of the LLM body so investigators don't lose context if the
  LLM is terse.
- Reuses s.llm (set via Server.SetLLM in Phase 2) — no new config
  knob, no per-feature gating. Asymmetric opt-out (yes-elaborate,
  no-CI-triage) deferred until there's actual demand.

Tests:
- enrichCIInstructions: nil client, LLM 500, empty body all return
  fallback unchanged.
- enrichCIInstructions: success path produces enriched body with
  metadata header preserved; user prompt contains repo/branch/SHA.
- enrichCIInstructions: real git repo (init + 2 commits) → recent
  commits appear in user prompt.
- Webhook handler regression guard: no-LLM path produces the exact
  legacy template substrings.
- Webhook handler with LLM stubbed: task instructions contain LLM
  body + metadata header.

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

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</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>feat(executor): add LocalRunner and OpenAI-compat LLM client</title>
<updated>2026-04-28T09:24:43+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-28T09:24:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=0865afc43be562dbe14528e4299b9e213b54cc93'/>
<id>urn:sha1:0865afc43be562dbe14528e4299b9e213b54cc93</id>
<content type='text'>
Phase 1 of "local OSS models as agents" plan. Adds a third Runner
backed by any OpenAI-compatible HTTP server (Ollama, vLLM, LM Studio,
llama.cpp), and migrates the Gemini-CLI classifier to route through
the same client when configured.

Two-layer split: internal/llm.Client is the workhorse (HTTP, no Pool,
no DB) used directly by the classifier and any future internal helper
that needs cheap reasoning. internal/executor.LocalRunner is a thin
adapter implementing Runner for user-facing tasks. This avoids
Pool reentrancy/deadlock when sub-second internal calls fire from
inside Pool.execute().

Highlights:
- internal/retry: relocated runWithBackoff/IsRateLimitError/ParseRetryAfter
  into a shared package reused by executor and llm.
- internal/llm: Chat (non-streaming) and ChatStream (SSE) over
  /chat/completions with optional bearer auth, json_object response
  format, retry on 429/503, Retry-After parsing.
- internal/executor/LocalRunner: streams deltas into stdout.log in the
  same stream-json envelope ClaudeRunner emits, then writes one
  consolidated assistant block plus a result terminator so existing
  parsers (extractSummary, ParseChangestatFromOutput) work unchanged.
- internal/executor/Classifier: gains optional LLM field; uses
  json_object response format (no markdown-fence cleanup needed).
  Falls back to Gemini-CLI subprocess when LLM is nil.
- Pool.skipClassification: now skips only when the requested agent
  type is registered, so unknown types still reach the load balancer.
- Storage: additive tokens_in/tokens_out ALTERs on executions; CLI
  runners record cost_usd as before, LocalRunner records 0 + tokens.
- Config: [local_model] section (endpoint, model, timeout_seconds,
  default_temperature, api_key). Empty endpoint = no LocalRunner
  registered, classifier falls back to Gemini.

Pre-existing test issues fixed in passing:
- claude_test.go setupSandbox callsites updated to current signature.
- gemini_test.go TestParseGeminiStream skipped (asserts unimplemented
  GeminiRunner stream-error parsing; tracked separately).

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

https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
</content>
</entry>
<entry>
<title>docs: task checker + story ship implementation plan</title>
<updated>2026-04-04T08:36:09+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-04T08:36:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=027e85fc8c38267e6a04d8361febbacaff18f48a'/>
<id>urn:sha1:027e85fc8c38267e6a04d8361febbacaff18f48a</id>
<content type='text'>
Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: task checker agent and story ship gate design spec</title>
<updated>2026-04-04T07:40:45+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-04T07:40:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=68e43ca49a3002218ec057296c14f1c02d9f3237'/>
<id>urn:sha1:68e43ca49a3002218ec057296c14f1c02d9f3237</id>
<content type='text'>
Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
