<feed xmlns='http://www.w3.org/2005/Atom'>
<title>claudomator.git/internal/storage, 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-07-11T05:38:25+00:00</updated>
<entry>
<title>fix(scheduler): make arbitration fail-closed on a missing verdict, seed planner's system prompt</title>
<updated>2026-07-11T05:38:25+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-11T05:37:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=204fe6c536d5d3bca2cb712f66b1d63054807dac'/>
<id>urn:sha1:204fe6c536d5d3bca2cb712f66b1d63054807dac</id>
<content type='text'>
A live production run (2026-07-11) showed finalizeArbitration's 'no
verdict reported = approve' default silently shipping work an evaluator
had already flagged as factually wrong -- the arbitration agent never
called report_verdict because the planner role had no system prompt
telling it to. finalizeArbitration now treats a missing verdict the same
as an explicit rejection (fail-closed), and SeedRoleConfigs now also
seeds planner with a prompt that explicitly mandates calling
report_verdict before finishing.
</content>
</entry>
<entry>
<title>feat(storage): seed the builder role's system prompt on server startup</title>
<updated>2026-07-10T20:22:30+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator</email>
</author>
<published>2026-07-10T20:22:30+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=3728fb07ddc9c3a85cf4f2152b35e090618f87db'/>
<id>urn:sha1:3728fb07ddc9c3a85cf4f2152b35e090618f87db</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(task,storage): add Task.AcceptanceCriteria, mirroring story.Story's pattern</title>
<updated>2026-07-09T03:02:18+00:00</updated>
<author>
<name>Claudomator Agent</name>
<email>agent@claudomator.local</email>
</author>
<published>2026-07-09T03:02:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=5ea8f50b0c43a23af6e65bce1fe057a355cb1929'/>
<id>urn:sha1:5ea8f50b0c43a23af6e65bce1fe057a355cb1929</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(storage): add ALTER TABLE migrations for stories columns added in schema redesign</title>
<updated>2026-07-04T09:56:07+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-04T09:56:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=d8f79c1912eccdc2c65679c4f60603f98f5eece2'/>
<id>urn:sha1:d8f79c1912eccdc2c65679c4f60603f98f5eece2</id>
<content type='text'>
The live DB has a stories table created before the full schema was
established. CREATE TABLE IF NOT EXISTS is a no-op on those DBs, so
epic_id, discovery_source, spec, acceptance_criteria_json, priority,
and root_task_id never landed — causing the CREATE INDEX on epic_id to
fail with "no such column" on startup.

Add an ALTER TABLE migration for each missing column, relying on the
existing isColumnExistsError guard for idempotency on fresh DBs.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DNDHfCtTZDbQueEV5sUBiD
</content>
</entry>
<entry>
<title>feat(web,api): add Budget/Roles dashboard -- final phase of the harness redesign (Phase 9b)</title>
<updated>2026-07-04T08:50:46+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-04T08:50:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=f8ae821240f33d615a9e91cdfeb6c026b7970782'/>
<id>urn:sha1:f8ae821240f33d615a9e91cdfeb6c026b7970782</id>
<content type='text'>
Two new tabs, matching Phase 9a's conventions: Budget (per-provider spend
meters, escalation funnel, spend-over-time) and Roles (version history,
draft activation, readable escalation-ladder view) -- the human-facing
surface for the token-husbanding value proposition and the Phase 5/8
versioned role-config system.

New backend (minimal, additive, matching existing endpoint conventions --
unauthenticated like /api/budget and /api/roles/*):
- internal/storage/dashboard.go: QueryEscalationFunnel (executions grouped
  by escalation_rung + agent, count + cost) and QuerySpendTimeseries (cost
  per provider bucketed hourly/daily, mirroring QueryDashboardStats'
  existing bucketing expressions). Documented honestly: rung 0 is not
  exclusively "resolved locally" -- escalation_rung defaults to 0
  uniformly, so non-role-typed executions (which never climb a ladder)
  land there too, alongside role-typed tasks genuinely resolved at tier 0.
  A role-only variant would need a join against tasks.config_json with no
  queryable role column on executions -- intentionally out of scope.
- internal/api/dashboard.go: GET /api/escalation-funnel, GET
  /api/spend-timeseries (both ?window=5h|24h|7d|&lt;duration&gt;, default 24h).
- internal/storage.ListRoleNames + GET /api/roles: the "which roles exist"
  gap -- there was no way to discover role names before this, only to list
  versions for a role you already knew the name of.

Chart-form decisions (dataviz skill, invoked before writing chart code):
horizontal stacked bar for the escalation funnel (rung order already
encodes the funnel shape positionally; color only needed for per-provider
segments within each rung); multi-line for spend-over-time; a fixed-order
categorical palette from the skill's validated palette.md slots for
provider identity (re-validated against this app's dark surface, passing);
a separate status (good/warning/critical) palette for budget meters,
deliberately distinct from both --state-* and the provider palette. Caught
and fixed a real bug during visual QA: converging near-zero end-labels on
the spend chart were overlapping (an anti-pattern the skill explicitly
flags) -- fixed with a 14px minimum-gap check before direct-labeling an
endpoint, leaning on the legend/tooltip otherwise.

Verified with a real running server, real seeded data (65 executions across
rungs 0-2 with a realistic provider mix, 2 roles with active/draft/retired
role_configs versions written directly via internal/storage), and a real
headless-browser session (reusing Phase 9a's Chromium/proxy scaffinding):
confirmed correct rung totals/percentages, provider legends, a 3-line spend
chart, live window-selector re-render, correct active-version highlighting
on the role panel, and a real Activate click on a draft version -- verified
via both DOM re-render and a direct backend GET that it truly persisted.

go build/vet/test -race -count=1 all pass, full suite. node --test
web/test/*.mjs: 291/291 passing (16 new).

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(story): add Epic/Story data model + REST CRUD (Phase 7a)</title>
<updated>2026-07-03T23:46:26+00:00</updated>
<author>
<name>Claude Sonnet 5</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-07-03T23:46:26+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/claudomator.git/commit/?id=392c7c1ada310b2f928dca89b75ba628478f7694'/>
<id>urn:sha1:392c7c1ada310b2f928dca89b75ba628478f7694</id>
<content type='text'>
Pure data model + CRUD -- no orchestration behavior yet (that's Phase 7b).
Revives ADR-007's stories concept (adapted; ADR-007 itself was deleted as
"more machinery than the usage pattern needed") while staying lean: reuses
the existing events/projects/task-tree machinery rather than building a
parallel hierarchy.

- internal/story: Epic/Story types. Epic is a loosely-scoped initiative
  (OPEN/CLOSED, no execution semantics). Story is a shippable slice of work
  realized by a task tree rooted at root_task_id; epic_id/project_id/
  root_task_id are loose references (no FK enforcement, matching the
  codebase's existing tolerance for unmatched reference strings like
  tasks.project).
- storage: new epics/stories tables (additive migrations) + CRUD methods.
  Story status is intentionally unvalidated pass-through in this phase --
  no task.ValidTransition-style state machine, since there's no orchestrator
  yet to make transitions meaningful.
- event: 9 new Kind constants for the ceremony this layer will eventually
  drive (epic_proposed, discovery_proposed, framing_decided, groomed,
  prioritized, eval_verdict, arbitration_decided, retro_captured,
  human_accepted) -- defined but nothing emits them yet.
- api: GET/POST /api/epics, GET/PUT /api/epics/{id}, GET /api/epics/{id}/stories,
  GET/POST /api/stories, GET/PUT /api/stories/{id}, and GET
  /api/stories/{id}/task-tree -- BFS walk from root_task_id following both
  parent_task_id children and depends_on-edge dependents (visited-set
  guarded), returning a flat node list for a later UI phase to render as a
  graph. Unauthenticated, matching the existing projects/tasks endpoints'
  posture.

go build/vet/test -race -count=1 all pass, 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(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>
</feed>
