<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/internal/cli, 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>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): 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>fix: tie pool submissions to server lifecycle context</title>
<updated>2026-04-11T18:26:42+00:00</updated>
<author>
<name>Claude Agent</name>
<email>agent@claudomator</email>
</author>
<published>2026-04-11T18:26:42+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=49cdcd70f275c858b0511a2e88ab30a48b157fa3'/>
<id>urn:sha1:49cdcd70f275c858b0511a2e88ab30a48b157fa3</id>
<content type='text'>
Fix 1 (server.go): Replace context.Background() with s.ctx in
handleAnswerQuestion, handleResumeTimedOutTask, and handleRunTask.
Add a ctx field to Server (defaulting to context.Background()) and
a SetContext method so the serve command can wire in the signal-
cancellable lifecycle context.

Fix 2 (serve.go): Call srv.SetContext(ctx) before StartHub so all
pool submissions use the server's root context (already cancelled
on SIGTERM/SIGINT). Pool.Shutdown and its wiring were already present.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: remove drain-lock circuit breaker that halted all executions after 3 consecutive failures</title>
<updated>2026-04-03T08:39:32+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-03T08:39:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=5aa6a15ffdf68a8dbe12eb0fdfff93deafb9da10'/>
<id>urn:sha1:5aa6a15ffdf68a8dbe12eb0fdfff93deafb9da10</id>
<content type='text'>
Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: graceful shutdown — drain workers before exit (default 3m timeout)</title>
<updated>2026-03-26T09:14:14+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-26T09:09:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=3f9843b34d7ae9df2dd9c69427ecab45744b97e9'/>
<id>urn:sha1:3f9843b34d7ae9df2dd9c69427ecab45744b97e9</id>
<content type='text'>
- Add workerWg to Pool; Shutdown() closes workCh and waits for all
  in-flight execute/executeResume goroutines to finish
- Signal handler now shuts down HTTP first, then drains the pool
- ShutdownTimeout config field (toml: shutdown_timeout); default 3m
- Tests: WaitsForWorkers and TimesOut

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: Phase 4 — story-aware execution, branch clone, story completion check, deployment status</title>
<updated>2026-03-23T07:12:08+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator.dev</email>
</author>
<published>2026-03-23T07:12:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=b2e77009c55ba0f07bb9ff904d9f2f6cc9ff0ee2'/>
<id>urn:sha1:b2e77009c55ba0f07bb9ff904d9f2f6cc9ff0ee2</id>
<content type='text'>
- ContainerRunner: add Store field; clone with --reference when story has a
  local project path; checkout story branch after clone; push to story branch
  instead of HEAD
- executor.Store interface: add GetStory, ListTasksByStory, UpdateStoryStatus
- Pool.handleRunResult: trigger checkStoryCompletion when a story task succeeds
- Pool.checkStoryCompletion: transitions story to SHIPPABLE when all tasks done
- serve.go: wire Store into each ContainerRunner
- stories.go: update createStoryBranch to fetch+checkout from origin/master base;
  add GET /api/stories/{id}/deployment-status endpoint
- server.go: register deployment-status route
- Tests: TestPool_CheckStoryCompletion_AllComplete/PartialComplete,
  TestHandleStoryDeploymentStatus

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: populate RepositoryURL from project registry in executor (ADR-007)</title>
<updated>2026-03-23T06:50:10+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-23T06:50:10+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=bc62c3545bbcf3f9ccc508cdc43ce9ffdb5dfad0'/>
<id>urn:sha1:bc62c3545bbcf3f9ccc508cdc43ce9ffdb5dfad0</id>
<content type='text'>
- Add GetProject to Store interface used by executor
- Resolve RepositoryURL from project registry when task.RepositoryURL is empty
- Call SeedProjects at server startup so the project registry is populated
- Add GetProject stub to minimalMockStore in executor tests

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: executor reliability — per-agent limit, drain gate, pre-flight creds, auth recovery</title>
<updated>2026-03-21T23:18:50+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator.local</email>
</author>
<published>2026-03-21T23:18:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=8dca9bbb0baee59ffe0d3127180ef0958dda8b91'/>
<id>urn:sha1:8dca9bbb0baee59ffe0d3127180ef0958dda8b91</id>
<content type='text'>
- maxPerAgent=1: only 1 in-flight execution per agent type at a time; excess tasks are requeued after 30s
- Drain gate: after 2 consecutive failures the agent is drained and a question is set on the task; reset on first success; POST /api/pool/agents/{agent}/undrain to acknowledge
- Pre-flight credential check: verify .credentials.json and .claude.json exist in agentHome before spinning up a container
- Auth error auto-recovery: detect auth errors (Not logged in, OAuth token has expired, etc.) and retry once after running sync-credentials and re-copying fresh credentials
- Extracted runContainer() helper from ContainerRunner.Run() to support the retry flow
- Wire CredentialSyncCmd in serve.go for all three ContainerRunner instances
- Tests: TestPool_MaxPerAgent_*, TestPool_ConsecutiveFailures_*, TestPool_Undrain_*, TestContainerRunner_Missing{Credentials,Settings}_FailsFast, TestIsAuthError_*, TestContainerRunner_AuthError_SyncsAndRetries

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