<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/internal/executor/executor.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-04T05:05:36+00:00</updated>
<entry>
<title>feat(story,role): add retro ceremony -- closes the self-improvement loop (Phase 8)</title>
<updated>2026-07-04T05:05:36+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-04T05:05:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=8cb291ad7cdb317ff80947278ee055b1a4925b41'/>
<id>urn:sha1:8cb291ad7cdb317ff80947278ee055b1a4925b41</id>
<content type='text'>
The final mechanism the versioned role-config model (Phase 5) was built for:
when a story reaches DONE, StoryOrchestrator spawns a retro-role task that
reflects on the story's full history and proposes draft role_configs
versions for a human to review and activate via the existing (unchanged)
POST /api/roles/{role}/activate.

- AgentChannel gains a 6th method, ProposeRoleConfig(ctx, role.RoleConfig)
  (version, err), following ProposeEpic's precedent (Phase 7c): a structured
  tool call, not summary-parsing. storeChannel.ProposeRoleConfig calls the
  same Store.CreateRoleConfig the human-facing POST /api/roles/{role}/versions
  endpoint already uses (proposed_by: "retro"), landing a new draft row
  without touching whatever's currently active. Wired through both
  transports exactly like ProposeEpic: internal/agentloop/tools.go (native
  loop) and internal/executor/agentmcp.go (MCP).
- StoryOrchestrator.Tick now routes a story at status DONE to a new
  processRetro stage instead of processStory -- a sibling stage, not a
  continuation, since the Builder-&gt;Evaluators-&gt;Arbitration chain is long
  settled by then. processRetro only *reads* that settled pipeline
  (read-only findEvaluators/findArbitration counterparts to
  ensureEvaluators/ensureArbitration -- it never spawns/mutates
  Builder-pipeline tasks) to locate the Arbitration task the retro task
  depends on, then spawns (idempotently -- checks for an existing
  retro-role dependent first) one retro-role task with instructions
  assembled from the story's spec/acceptance-criteria, full task tree, per-
  task cost/escalation history, active role_configs per role encountered,
  and the story's own event stream (evaluator verdicts, arbitration
  decision).
- event.KindRetroCaptured (attached to the story's ID, matching
  KindEvalVerdict/KindArbitrationDecided's convention) fires once the retro
  task completes (auto-accepted like every other pipeline task), aggregating
  every event.KindRoleConfigProposed the retro task recorded (one per
  propose_role_config call) into {task_id, proposals: [{role, version}],
  summary} -- the summary is the "capturing lessons" half of this ceremony,
  the proposals are the versioned-config half.
- Human activation is completely untouched: drafts land through the
  identical CreateRoleConfig/config_json path Phase 5's endpoints already
  handle, confirmed via existing role-endpoint tests passing unmodified.

go build/vet/test -race -count=1 all pass, full suite (20 packages) -- one
run hit a known, pre-existing, intermittent flake under full-suite load
(unrelated to this phase's files) that did not reproduce on two immediate
reruns, both in isolation and full-suite.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>feat(story,scheduler): add epic-proposal tool + AskUser-timeout escalation (Phase 7c)</title>
<updated>2026-07-04T04:39:28+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-04T04:39:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=04b6e7eef473cb6eb69e345a4ea08243a8713077'/>
<id>urn:sha1:04b6e7eef473cb6eb69e345a4ea08243a8713077</id>
<content type='text'>
Two independent pieces, completing Phase 7.

Epic-proposal tool: AgentChannel gains a 5th method, ProposeEpic(ctx,
EpicProposal{Name, Description, StoryIDs}) (epicID, err), implemented on
storeChannel -- matches an existing epic by exact name or creates one
(DiscoverySource: "agent"), sets epic_id on each resolvable story (skips,
doesn't fail, on an unresolved ID), emits KindEpicProposed attached to the
epic's own ID with payload {epic_id, name, story_ids}. Wired into both
transports exactly like Phase 6 wired role into spawn_subtask: a new
propose_epic tool in the native tool-use loop (internal/agentloop/tools.go)
and the MCP transport (internal/executor/agentmcp.go). This is the mechanism
for a discovery/planner-role agent to act on its own judgment that several
stories it's been given form one cohesive initiative -- the judgment itself
lives in the calling agent's instructions/model, not in this code.

AskUser-timeout escalation: extends the existing Scheduler (Phase 5's
retry-then-escalate watcher) rather than adding a new component, since
"stuck task needs escalation" is exactly what it already does. Finds
role-typed BLOCKED tasks whose question has been outstanding longer than
SchedulerConfig.AskUserTimeoutSeconds (default 10 minutes) using
task.UpdatedAt as the outstanding-since timestamp -- no new column needed,
since UpdateTaskQuestion already stamps it the instant a question is
recorded and nothing else touches the row while BLOCKED. Resolves the next
ladder tier from the latest execution's EscalationRung, records the
system-authored fallback answer as an audit-trail task.Interaction, clears
the question, sets the new tasks.needs_review flag, emits KindEscalated
(now carrying a trigger field: "failure" vs "ask_user_timeout" for the
existing failure-retry path vs this one), and resumes via Pool.SubmitResume
at the escalated tier -- degrading to same-tier resume with final:true if
the ladder's exhausted or no role config exists, since unblocking the task
takes priority over having somewhere higher to escalate to.
GET /api/tasks?needs_review=true surfaces auto-decided tasks for human
review.

go build/vet/test -race -count=1 all pass, full suite (20 packages), run
twice to rule out flakiness in the new tests. (One pre-existing, unrelated
test -- TestHandleRunTask_CascadesRetryToFailedDeps, a tempdir-cleanup race
-- appeared once under full-suite load per the implementing agent's report
and did not reproduce in this verification's runs either; not a regression
from this work.)

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
</content>
</entry>
<entry>
<title>feat(executor): add DAG auto-cascade-fail + role-typed subtask spawning (Phase 6)</title>
<updated>2026-07-03T23:22:42+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T23:22:42+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=997cd8b56bc086a02b9c7c006dd62b07b9fcd2f3'/>
<id>urn:sha1:997cd8b56bc086a02b9c7c006dd62b07b9fcd2f3</id>
<content type='text'>
Two prerequisites for safe parallel evaluator fan-out (later phase):

1. Auto-cascade-fail: previously, a failed task's dependents just sat
   PENDING/QUEUED forever (or until something eventually tried to dispatch
   them and discovered the dependency was dead). Pool.cascadeFail now fires
   right after a task lands in a terminal failure state (FAILED/TIMED_OUT/
   CANCELLED/BUDGET_EXCEEDED, from handleRunResult, the budget-gate reject
   path, and the checkDepsReady dependency-failure path), recursively
   cancelling every not-yet-run dependent (PENDING/QUEUED only -- RUNNING and
   terminal states are left alone) with a message referencing the upstream
   failure. A visited-set guards recursion, which turned out to be
   load-bearing rather than defense-in-depth: task creation does not prevent
   dependency cycles anywhere in this codebase.

   Correction to an earlier assumption: internal/executor's
   waitForDependencies is dead code, never called. The live mechanism is
   checkDepsReady, invoked synchronously in execute() with a self-requeue via
   time.AfterFunc. Added a freshness re-check (GetTask, bail if no longer
   QUEUED) at the top of that block so a task cascade-cancelled while sitting
   in the requeue loop stops silently instead of hitting an invalid
   CANCELLED-&gt;CANCELLED transition or, worse, still getting dispatched.

2. storeChannel.SpawnSubtask hardcoded Agent.Type: "claude" on every spawned
   child regardless of what role it should play -- a hard blocker for a
   Planner/Builder task spawning role-typed evaluator subtasks. SubtaskSpec
   (internal/agentchannel) gains a Role field; when set, the child task gets
   Agent.Role instead of a hardcoded Type, so Phase 5's role-resolution picks
   provider/model from that role's escalation ladder. spec.Role == "" (every
   existing caller) preserves today's exact behavior byte-for-byte -- proven
   by an explicit regression test, not just new-feature coverage. Threaded
   the new `role` parameter through both spawn_subtask transports: the native
   tool-use loop (internal/agentloop/tools.go) and the MCP tool exposed to
   ContainerRunner-driven claude/gemini agents (internal/executor/agentmcp.go).

go build/vet/test -race -count=1 all pass, full suite (20 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(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>chore: remove stories/checker backend dead code</title>
<updated>2026-06-05T09:21:32+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator</email>
</author>
<published>2026-06-05T09:21:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=0eb0b79396663c4901597becc6857a4cd795c58e'/>
<id>urn:sha1:0eb0b79396663c4901597becc6857a4cd795c58e</id>
<content type='text'>
Remove the dead stories/checker backend tracked as design debt in CLAUDE.md:
- Delete internal/api/stories.go, stories_test.go, elaborate.go
- Delete internal/task/story.go, story_test.go
- Remove story/checker columns from storage schema and all CRUD methods
- Remove checkStoryCompletion, spawnCheckerTask, triggerStoryDeploy,
  createValidationTask, checkValidationResult, ShipStory, CheckStoryCompletion
  from executor; simplify handleRunResult
- Remove story/checker fields from task.Task (StoryID, AcceptanceCriteria,
  CheckerForTaskID, CheckerReport)
- Remove story routes and geminiBinPath from API server
- Move claudeJSONResult/extractJSON from elaborate.go to validate.go
- Rename ensureStoryBranch -&gt; ensureBranch in ContainerRunner
- Fix git identity in bare-repo tests; fix initial-branch to use main

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>executor: deduplicate worker slots and fix git command execution</title>
<updated>2026-06-04T07:40:46+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-04T07:40:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=fa3ed5220a28f326d443726233c37e1a7df0ced5'/>
<id>urn:sha1:fa3ed5220a28f326d443726233c37e1a7df0ced5</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(executor): skip checker task for local-runner executions</title>
<updated>2026-06-04T04:58:02+00:00</updated>
<author>
<name>Claude</name>
<email>claude@anthropic.com</email>
</author>
<published>2026-06-04T04:58:02+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=3199ba2a9927d9b259b3f0270ad108561e6e4b6e'/>
<id>urn:sha1:3199ba2a9927d9b259b3f0270ad108561e6e4b6e</id>
<content type='text'>
Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: release dispatch slot on all early-return paths in execute()</title>
<updated>2026-06-03T23:31:20+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-03T23:31:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=ad4fcd22ec6f7408d1efc6a2bb0009c2377f57a6'/>
<id>urn:sha1:ad4fcd22ec6f7408d1efc6a2bb0009c2377f57a6</id>
<content type='text'>
p.active was incremented by dispatch() before launching execute(), but
execute() had four early-return paths (budget gate, dep terminal failure,
deps not ready, per-agent limit) that returned before the defer
decActiveAgent was registered. Each such return leaked a slot, eventually
saturating maxConcurrent and permanently blocking dispatch() on doneCh.

Introduce releaseDispatchSlot() (decrements p.active + signals doneCh) and
defer it at the very top of execute() and executeResume(), so every exit
path releases the dispatch slot. Remove p.active and doneCh management from
decActiveAgent so it only handles the per-agent counter.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: embed execution log in checker instructions and add noopChannel to tests</title>
<updated>2026-06-03T23:04:40+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-03T23:04:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=16110bec8b6ece5837560fb626ab30d8cdec0e81'/>
<id>urn:sha1:16110bec8b6ece5837560fb626ab30d8cdec0e81</id>
<content type='text'>
The checker task previously had no context about what the agent did —
it tried to reach the API (potentially unavailable), search /home for
artifacts, and clone the repo, all of which fail for LocalRunner tasks.

Now spawnCheckerTask reads the tail of the execution's stdout.log and
embeds it directly in the checker's instructions so the checker can
verify output without needing Docker, API access, or repo cloning.

Also fixes container_test: adds noopChannel to satisfy the AgentChannel
parameter added by the OSS Runner interface.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(executor): budget-gate the dispatcher — reroute to local or block (Phase 6)</title>
<updated>2026-05-26T20:31:24+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-26T20:31:24+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=32715355fe2eed321df4f7083dfe580d35f8a62a'/>
<id>urn:sha1:32715355fe2eed321df4f7083dfe580d35f8a62a</id>
<content type='text'>
The pool now consults a BudgetGate before taking an agent slot. If running on
the chosen paid provider could breach its rolling window (estimated by the
task's max_budget_usd), it reroutes to the free local runner when one is
registered; otherwise it stops before running and marks the task
BUDGET_EXCEEDED. serve.go builds a budget.Accountant from [budget] config and
wires it into the pool (only when limits are configured).

Allows the QUEUED → BUDGET_EXCEEDED transition, since the gate blocks a queued
task before it ever reaches RUNNING. Covered by pool tests for both the block
and reroute paths against a fake gate.

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