| Age | Commit message (Collapse) | Author |
|
redesign (Phase 9b)
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|<duration>, 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
(Phase 7c)
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
Builder->Evaluators->Arbitration->accept (Phase 7b)
A deterministic, poll-based watcher (internal/scheduler.StoryOrchestrator,
sibling to the Phase 5 Scheduler) that drives a story.Story through its
execution pipeline, rather than relying on an LLM agent to correctly
orchestrate its own fan-out via tool calls.
Mechanism: polling, not a handleRunResult hook. Every task the orchestrator
watches (a story's root/Builder task, 4 Evaluators, Arbitration) is
top-level (no ParentTaskID), and executor.Pool.handleRunResult only ever
lands a top-level task at READY or BLOCKED -- never COMPLETED directly, since
that transition normally requires a human/chatbot POST /api/tasks/{id}/accept
in a different package. A handleRunResult hook would never observe it;
polling doesn't care how/whether a task reached a given state.
Stages: Builder COMPLETED -> spawn 4 role-typed Evaluator tasks
(evaluator_quality/security/correctness/performance, DependsOn: [builder],
no ParentTaskID -- true DAG siblings, not delegated subtasks) + story ->
VALIDATING. Each Evaluator COMPLETED -> emit KindEvalVerdict (attached to
the story's ID, so one GET /api/stories/{id}/events call surfaces every
verdict). All 4 Evaluators COMPLETED -> spawn 1 Arbitration task
(role: planner, DependsOn: all 4 evaluator IDs). Arbitration COMPLETED ->
emit KindArbitrationDecided, story -> REVIEW_READY. POST
/api/stories/{id}/accept (mirrors handleAcceptTask) -> DONE, emits
KindHumanAccepted.
Fixes a gap caught before merging: since none of Builder/Evaluators/
Arbitration have a ParentTaskID, none of them auto-complete -- each would
otherwise need a separate manual /api/tasks/{id}/accept, meaning 6 human
clicks per story before ever reaching the intended single story-level gate.
StoryOrchestrator.autoAccept now transitions each of these specific tasks
READY->COMPLETED itself (via the same validated Store.UpdateTaskState path
acceptTask uses), scoped only to tasks already established as part of a
story's pipeline (root task, or role-matched dependents from
ensureEvaluators/ensureArbitration) -- never a blanket sweep of unrelated
READY tasks. This makes POST /api/stories/{id}/accept the system's only
required human touchpoint for the whole chain, matching the design goal
that story (not task/subtask) is the human-interaction atom.
Idempotency: structural for task-creation stages (ensureEvaluators/
ensureArbitration check ListDependents for already-existing role-matched
tasks before creating -- crash/restart-safe); story.Status=="VALIDATING"
gates the Arbitration->REVIEW_READY write (nothing further downstream to
check structurally there); an in-memory handledVerdicts set (mirrors
Scheduler.handled) dedupes per-evaluator KindEvalVerdict emission across
poll ticks, resetting harmlessly on restart.
Documented simplification: finalizeArbitration never parses the Arbitration
summary for approve/reject -- always routes to REVIEW_READY; NEEDS_FIX is
manually settable via PUT /api/stories/{id}. A later phase could close this
with a dedicated verdict-reporting AgentChannel method instead of parsing
free text.
go build/vet/test -race -count=1 all pass, full suite (20 packages).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
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 -> ensureBranch in ContainerRunner
- Fix git identity in bare-repo tests; fix initial-branch to use main
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
loopback-only bind
Key features from OSS branch:
- Budget gating: rolling per-provider spend caps with BUDGET_EXCEEDED task state
- Agent MCP back-channel: runners mint tokens; /mcp endpoint resolves them
- Chatbot MCP server at /chatbot/mcp (requires api_token in config)
- Event timeline: unified event stream replacing ad-hoc question/summary flows
- Loopback-only default bind (127.0.0.1:8484); external_bind_allowed=true to expose
- repository_url required on task creation (enforces traceability)
- Auto-checker: spawns verification task after each execution
Conflict resolutions:
- serve.go: keep cfg.Runners.XEnabled() conditional registration from main + agentRegistry from oss
- config.go: keep RunnersConfig from main + BudgetConfig/ExternalBindAllowed from oss
- server.go: handleStaticFiles (base-path rewrite) kept; deduplicated duplicate from both sides
- executor/claude.go: accepted oss deletion (ClaudeRunner replaced by ContainerRunner)
- container_test.go: added noopChannel + AgentChannel parameter to Run call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Allows running multiple instances (e.g. /claudomator and /claudomator-oss)
behind a reverse proxy. The server rewrites the base-path meta tag in
index.html at request time rather than baking it into the embed.
Also adds --branch support to deploy script for isolated branch builds
via git worktrees.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- ContainerRunner now resolves local project path for non-story (CI)
tasks, cloning from the local bare repo instead of the HTTPS GitHub
URL to avoid auth failures on the host.
- CI failure tasks now use SSH URLs (git@github.com:...) instead of
HTTPS to avoid credential prompts even when no local path is found.
- GitHub push webhook events now trigger an async git fetch into the
local bare repo, keeping the local mirror in sync when work happens
directly on GitHub.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds GET /api/budget returning per-provider rolling-window headroom (empty when
budget gating is unconfigured), wired from serve.go via SetBudget. The web UI
polls it and renders a chip per limited provider in the header, flagging any
under 20% remaining. New pure JS helpers formatBudgetHeadroom/
renderBudgetHeadroom are unit-tested; the endpoint is covered by Go handler
tests (empty/reports/500). UI render not browser-verified in this environment.
Completes Phase 6: spend accounting + dispatcher gating + observability surface.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
Adds GET /api/tasks/{id}/events (seq-ordered, ?since_seq for incremental
polling) as the timeline data source, and a per-task Timeline section in the
web UI that fetches and renders the observability event stream. New pure,
unit-tested JS helpers: formatEventText, renderEventTimeline, fetchTaskEvents.
The timeline load is async and guarded on a global fetch so the synchronous
renderTaskPanel path (and its DOM-mock unit tests) is unaffected. Verified via
Go endpoint tests and node --test (272 JS tests pass). Note: the live in-browser
panel render was not exercised in this environment — only unit-level coverage.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
Chatbots now elaborate tasks in-conversation and submit them via the chatbot
MCP server, so the POST /api/tasks/elaborate shell-out path is no longer needed.
Removes the route, handleElaborateTask, and the task-elaborate-only helpers
(buildElaboratePrompt, sanitizeElaboratedTask, readProjectContext,
appendRawNarrative, elaborateWith{Claude,Local,Gemini}, and the elaboratedTask/
elaboratedAgent types) plus their dedicated tests.
Shared helpers (extractJSON, claude/geminiBinaryPath, claude/geminiJSONResult,
elaborateTimeout, the per-IP limiter) are retained — the story-elaborate
endpoint and task-validate endpoint still use them. Stories themselves are
untouched here; their removal stays in the Phase 8 cleanup pass.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
Adds a chatbot-facing MCP server mounted at /chatbot/mcp, guarded by the shared
API bearer token (new config api_token, wired through SetAPIToken). Tools:
submit_task, list_tasks, get_task, get_events, answer_question, accept_task,
reject_task, cancel_task. submit_task creates and immediately queues a task,
resolving the repository URL from a named project when not given explicitly.
To keep the REST API and the MCP surface from drifting, the accept/reject/
cancel/answer operations are extracted into a shared service layer (taskops.go)
with typed errors mapped to REST status codes; the existing HTTP handlers now
delegate to it. The endpoint is only served when a token is configured and
presented as a bearer.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
ContainerRunner now mints a per-task MCP token (from an injected Registry),
writes a claude mcp-config into the workspace pointing at the host agent MCP
server over host.docker.internal with that bearer, and adds --mcp-config to the
in-container claude invocation. The token is revoked when the run ends. After
the run, a buffered ask_user (PendingQuestion on the channel) is converted into
a BlockedError — the MCP path to BLOCKED — with the file-based question.json
kept as a fallback for in-flight tasks started on the old wire.
The API server mounts the StreamableHTTP MCP handler at POST/GET/DELETE /mcp
when a registry is provided; serve.go constructs one Registry shared by the
runners (mint) and the server (resolve). Minting is skipped for gemini agents.
Tests: writeMCPConfig output shape, buildInnerCmd flag presence/absence, and an
api-level end-to-end MCP tool call through NewServer/Handler proving the route
is mounted (and absent without a registry).
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
UpdateTaskState now writes a state_change event in the same transaction
as the row update, so the event stream and task state never diverge.
Adds UpdateTaskStateBy(id, newState, actor) for explicit actor
attribution; UpdateTaskState delegates with ActorSystem (correct for the
executor, which drives the state machine). RejectTask and
ResetTaskForRetry also emit state_change events attributed to the user.
API accept and cancel handlers now attribute their transitions to the
user via UpdateTaskStateBy. The executor's Store interface is unchanged,
so no test doubles needed updating.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
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 <noreply@anthropic.com>
|
|
Three related bugs fixed:
1. maybeUnblockParent: guard against promoting QUEUED leaf tasks (no
subtasks) to READY. The vacuously-true 'all subtasks done' check was
advancing tasks that stalled in QUEUED (due to a prior SQLite lock
error) to READY on server restart via RecoverStaleBlocked, despite
having only failed executions and no commits.
2. checkStoryCompletion: require COMPLETED (not just READY) for all
top-level tasks before advancing a story to SHIPPABLE. READY means
the checker agent is still pending or the task awaits human review;
a story with READY tasks is not ready to ship.
3. handleAcceptTask: call CheckStoryCompletion after a task is accepted
so stories with parent tasks (whose subtasks are all done and then
the parent is manually accepted) can auto-advance to SHIPPABLE.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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
|
|
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 <noreply@anthropic.com>
|
|
auto-deploy
- checkStoryCompletion now guards against re-running on already-SHIPPABLE stories
and no longer auto-triggers triggerStoryDeploy on completion
- New Pool.ShipStory method validates SHIPPABLE state then fires triggerStoryDeploy
- POST /api/stories/{id}/ship route registered and handleShipStory handler added
- Two new tests: 202 for SHIPPABLE story, 409 for non-SHIPPABLE story
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
consecutive failures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- 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 <noreply@anthropic.com>
|
|
When /run is called on a CANCELLED/FAILED task that has deps in a terminal
failure state, automatically reset and resubmit those deps so the task
isn't immediately re-cancelled by the pool's dep check.
Also update reset-failed-tasks script to handle CANCELLED tasks and clean
up preserved sandbox workspaces.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
With maxPerAgent=1, tasks with DependsOn were entering waitForDependencies
while holding the per-agent slot, preventing the dependency from ever running.
Fix: check deps before taking the slot. If not ready, requeue without holding
activePerAgent. Also accept StateReady (leaf tasks) as a satisfied dependency,
not just StateCompleted.
Add startedCh to pool and broadcast task_started WebSocket event when a task
transitions to RUNNING, so the UI immediately shows the running state during
the clone phase instead of waiting for completion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
check, deployment status
- 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 <noreply@anthropic.com>
|
|
Adds GET /api/version endpoint and uses the first 6 hex chars of the
commit hash to derive an HSL hue for the header h1 logo color.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- POST /api/stories/elaborate: runs Claude/Gemini against project LocalPath
to produce a structured story plan (name, branch_name, tasks, validation)
- POST /api/stories/approve: creates story + sequentially-wired tasks/subtasks
from the elaborate output and pushes the story branch to origin
- createStoryBranch helper: git checkout -b + push -u origin
- Tests: TestBuildStoryElaboratePrompt, TestHandleStoryApprove_WiresDepends
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
API endpoints
- internal/task/story.go: Story struct, StoryState constants, ValidStoryTransition
- internal/task/task.go: add StoryID field
- internal/storage/db.go: stories table + story_id on tasks migrations; CreateStory,
GetStory, ListStories, UpdateStoryStatus, ListTasksByStory; update all task
SELECT/INSERT to include story_id; scanTask extended with sql.NullString for story_id;
added modernc timestamp format to GetMaxUpdatedAt
- internal/storage/sqlite_cgo.go + sqlite_nocgo.go: build-tag based driver selection
(mattn/go-sqlite3 with CGO, modernc.org/sqlite pure-Go fallback) so tests run
without a C compiler
- internal/api/stories.go: GET/POST /api/stories, GET /api/stories/{id},
GET/POST /api/stories/{id}/tasks (auto-wires depends_on chain),
PUT /api/stories/{id}/status (validates transition)
- internal/api/server.go: register all story routes
- go.mod/go.sum: add modernc.org/sqlite pure-Go dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
creds, auth recovery
- 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 <noreply@anthropic.com>
|
|
- task.Project type + storage CRUD + UpsertProject + SeedProjects
- Remove AgentConfig.ProjectDir, RepositoryURL, SkipPlanning
- Remove ContainerRunner fallback git init logic
- Project API endpoints: GET/POST /api/projects, GET/PUT /api/projects/{id}
- processResult no longer extracts changestats (pool-side only)
- claude_config_dir config field; default to credentials/claude/
- New scripts: sync-credentials, fix-permissions, check-token
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- GET /api/stats?window=7d: pre-aggregated SQL queries for errors, throughput, billing
- Errors section: category summary (quota/rate_limit/timeout/git/failed) + failure table
- Throughput section: stacked hourly bar chart (completed/failed/other) over 7d
- Billing section: KPIs (7d total, avg/day, cost/run) + daily cost bar chart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
detection
- Detect Gemini TerminalQuotaError (daily quota) as BUDGET_EXCEEDED, not generic FAILED
- Surface container stderr tail in error so quota/rate-limit classifiers can match it
- Add agent_events table to persist rate-limit start/recovery events across restarts
- Add GET /api/agents/status endpoint returning live agent state + 24h event history
- Stats dashboard: agent status cards, 24h availability timeline, per-run execution table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Fix Critical Bug 1: Only remove workspace on success, preserve on failure/BLOCKED.
- Fix Critical Bug 2: Use correct Claude flag (--resume) and pass instructions via file.
- Fix Critical Bug 3: Actually mount and use the instructions file in the container.
- Address Design Issue 4: Implement Resume/BLOCKED detection and host-side workspace re-use.
- Address Design Issue 5: Consolidate RepositoryURL to Task level and fix API fallback.
- Address Design Issue 6: Make agent images configurable per runner type via CLI flags.
- Address Design Issue 7: Secure API keys via .claudomator-env file and --env-file flag.
- Address Code Quality 8: Add unit tests for ContainerRunner arg construction.
- Address Code Quality 9: Fix indentation regression in app.js.
- Address Code Quality 10: Clean up orphaned Claude/Gemini runner files and move helpers.
- Fix tests: Update server_test.go and executor_test.go to work with new model.
|
|
This commit implements the architectural shift from local directory-based
sandboxing to containerized execution using canonical repository URLs.
Key changes:
- Data Model: Added RepositoryURL and ContainerImage to task/agent configs.
- Storage: Updated SQLite schema and queries to handle new fields.
- Executor: Implemented ContainerRunner using Docker/Podman for isolation.
- API/UI: Overhauled task creation to use Repository URLs and Image selection.
- Webhook: Updated GitHub webhook to derive Repository URLs automatically.
- Docs: Updated ADR-005 with risk feedback and added ADR-006 to document the
new containerized model.
- Defaults: Updated serve command to use ContainerRunner for all agents.
This fixes systemic task failures caused by build dependency and permission
issues on the host system.
|
|
- POST /api/tasks now reads and stores the project field from request body
- GET /api/tasks/{id} returns project in response (via Task struct json tags)
- list command: adds PROJECT column to tabwriter output
- status command: prints Project line when non-empty
- Tests: TestProject_RoundTrip (API), TestListTasks_ShowsProject, TestStatusCmd_ShowsProject (CLI)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Apache fronts the Go service and only proxies /api/ paths; /sw.js hits
Apache's filesystem and 404s. Serve the service worker from
/api/push/sw.js with Service-Worker-Allowed: / so the browser allows
it to control the full origin scope. Update SW registration URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Web Push:
- WebPushNotifier with VAPID auth; urgency mapped to event type
(BLOCKED=urgent, FAILED=high, COMPLETED=low)
- Auto-generates VAPID keys on first serve, persists to config file
- push_subscriptions table in SQLite (upsert by endpoint)
- GET /api/push/vapid-key, POST/DELETE /api/push/subscribe endpoints
- Service worker (sw.js) handles push events and notification clicks
- Notification bell button in web UI; subscribes on click
File Drop:
- GET /api/drops, GET /api/drops/{filename}, POST /api/drops
- Persistent ~/.claudomator/drops/ directory
- CLAUDOMATOR_DROP_DIR env var passed to agent subprocesses
- Drops tab (📁) in web UI with file listing and download links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add deployment_status field to task list/get API responses for READY
tasks. The field includes deployed_commit, fix_commits, and includes_fix
so the UI can show whether the deployed server includes each fix.
- internal/api/task_view.go: taskView struct + enrichTask() helper
- handleListTasks/handleGetTask: return enriched taskView responses
- web/app.js: export renderDeploymentBadge(); add badge to READY cards
- web/test/deployment-badge.test.mjs: 8 tests for renderDeploymentBadge
- web/style.css: .deployment-badge--deployed / --pending styles
- server_test.go: 3 new tests (red→green) for enriched task responses
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds POST /api/webhooks/github that receives check_run and workflow_run
events and creates a Claudomator task to investigate and fix the failure.
- Config: new webhook_secret and [[projects]] fields in config.toml
- HMAC-SHA256 validation when webhook_secret is configured
- Ignores non-failure events (success, skipped, etc.) with 204
- Matches repo name to configured project dirs (case-insensitive)
- Falls back to single project when no name match found
- 11 new tests covering all acceptance criteria
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds GET /api/tasks/{id}/deployment-status which checks whether the
currently-deployed server binary includes the fix commits from the
task's latest execution. Uses git merge-base --is-ancestor to compare
commit hashes against the running version.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add ElaborationInput field to Task struct (task.go)
- Add DB migration and update CREATE/SELECT/scan in storage/db.go
- Update handleCreateTask to accept elaboration_input from API
- Update renderSubtaskRollup in app.js to prefer elaboration_input over description
- Capture elaborate prompt in createTask() form submission
- Update subtask-placeholder tests to cover elaboration_input priority
- Fix missing io import in gemini.go
When a task card is waiting for subtasks, it now shows:
1. The raw user prompt from elaboration (if stored)
2. The task description truncated at word boundary (~120 chars)
3. The task name as fallback
4. 'Waiting for subtasks…' only when all fields are empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
updates
|
|
- Add parseChangestatFromOutput/File helpers in internal/api/changestats.go
to parse git diff --stat summary lines from execution stdout logs
- Wire parser in processResult: after each execution completes, scan the
stdout log for git diff stats and persist via UpdateExecutionChangestats
- Tests: TestGetTask_IncludesChangestats (verifies processResult wiring),
TestListExecutions_IncludesChangestats (verifies storage round-trip)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Added an agent selector (Auto, Claude, Gemini) to the Start Next Task button.
- Updated the backend to pass query parameters as environment variables to scripts.
- Modified the executor pool to skip classification when a specific agent is requested.
- Added --agent flag to claudomator start command.
- Updated tests to cover the new functionality.
|
|
Interrupted tasks (CANCELLED, FAILED, BUDGET_EXCEEDED) now support session
resume in addition to restart. Both buttons are shown on the task card.
- executor: extend resumablePoolStates to include CANCELLED, FAILED, BUDGET_EXCEEDED
- api: extend handleResumeTimedOutTask to accept all resumable states with
state-specific resume messages; replace hard-coded TIMED_OUT check with a
resumableStates map
- web: add RESUME_STATES set; render Resume + Restart buttons for interrupted
states; TIMED_OUT keeps Resume only
- tests: 5 new Go tests (TestResumeInterrupted_*); updated task-actions.test.mjs
with 17 tests covering dual-button behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
When a task ran in a sandbox (/tmp/claudomator-sandbox-*) and went BLOCKED,
Claude stored its session under the sandbox path as the project slug. The
resume execution was running in project_dir, causing Claude to look for the
session in the wrong project directory and fail with "No conversation found".
Fix: carry SandboxDir through BlockedError → Execution → resume execution,
and run the resume in that directory so the session lookup succeeds.
- BlockedError gains SandboxDir field; claude.go sets it on BLOCKED exit
- storage.Execution gains SandboxDir (persisted via new sandbox_dir column)
- executor.go stores blockedErr.SandboxDir in the execution record
- server.go copies SandboxDir from latest execution to the resume execution
- claude.go uses e.SandboxDir as working dir for resume when set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Updated handleRunTask to use ResetTaskForRetry, which clears the agent type and model. This ensures that manually restarted tasks are always re-classified, allowing the system to switch to a different agent if the previous one is rate-limited. Also improved Claude quota-exhaustion detection.
|
|
response shapes
- handleListTasks: validate ?state= against known states, return 400 with clear
error for unrecognized values (e.g. ?state=BOGUS)
- handleCancelTask: replace {"status":"cancelling"|"cancelled"} with
{"message":"...","task_id":"..."} to match run/resume shape
- handleAnswerQuestion: replace {"status":"queued"} with
{"message":"task queued for resume","task_id":"..."}
- Tests: add TestListTasks_InvalidState_Returns400, TestListTasks_ValidState_Returns200,
TestCancelTask_ResponseShape, TestAnswerQuestion_ResponseShape,
TestRunTask_ResponseShape, TestResumeTimedOut_ResponseShape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add workspaceRoot field (default "/workspace") to Server struct
- Add SetWorkspaceRoot method on Server
- Update handleListWorkspaces to use s.workspaceRoot
- Add WorkspaceRoot field to Config with default "/workspace"
- Wire cfg.WorkspaceRoot into server in serve.go
- Expose --workspace-root flag on the serve command
- Add TestListWorkspaces_UsesConfiguredRoot integration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|