Use Realmroot only for human and machine authentication, authorize AMA through the browser user's multi-resource grant, and restore AK-managed Ed25519 Agent sessions.
Preserve Better Auth source tables while removing the mistaken Realmroot Agent binding and machine M2M paths.
BREAKING CHANGE: AK no longer accepts Realmroot Agent tokens or AMA machine credentials; active browser users must sign in again to authorize the AMA resource.
Replace Better Auth user, machine, and agent authentication with Realmroot native flows, migrate tenant ownership, and route AMA access through the cutover adapter. Preserve this deployed migration state as the baseline for the narrower user-auth-only redesign.
Detect supported leader runtimes by environment marker and require a matching live ancestor process plus session identity. Keep leader resources immutable while separating leader runtimes from AMA-backed worker runtimes.
Add heartbeat_enabled persistence and enforce a 1 hour minimum heartbeat interval.
Keep webhook triggers active when scheduled heartbeat is disabled, and expose the toggle through the UI and CLI.
Connect repo onboarding to the GitHub App and complete the installation
webhook, so per-repo App coverage is tracked and surfaced end to end.
- migration 0027: github_installations + github_installation_repositories
- githubInstallations.ts: owner-scoped install tracking; repoAppStatus
coverage computed on read (single source of truth, no denormalization)
- webhook: handle installation + installation_repositories events (was
pull_request only); githubApp.ts gains getInstallation /
listInstallationRepositories / recordInstallationFromSetup
- routes: new /api/github-app config|setup|repositories endpoints; repo
create/list/get return app_status; the authenticated config reports the
owner install status + accounts
- UI: install/connected state on Repositories + Account pages, dual-tab
Add dialog (browse-and-import from GitHub | manual URL), status badges
- drop the GITHUB_AGENT_TOKEN push-credential fallback: the App
installation token is the sole credential; an uninstalled repo fails
dispatch loudly instead of using a shared long-lived PAT
- env: add GITHUB_APP_SLUG; vite allowedHosts for a stable dev tunnel
Move AMA resource creation out of the dispatch path into the AK actions that own
them:
- Connect AMA -> provision the owner's AMA project + session-secret vault
(POST /api/ama/provision, called once from the account page after linking).
- Add Machine -> create the matching environment: local computer = self_hosted
(+ ama-runner via `ak start`), or Cloud Sandbox = a cloud environment
(POST /api/machines/cloud). Unlimited; machines.hosting distinguishes them.
- Create Agent -> create the AMA agent first, then persist the AK agent with its
ama_agent_id; if the AMA call fails the request errors and nothing is written.
- Dispatch only creates the session now: it reads the existing project and the
stored agents.ama_agent_id, picks an environment among the owner's machines
(cloud sandbox or local) by runtime, and 409s with "add a machine or cloud
sandbox" when none fits. No more per-owner lazy cloud singleton.
Agent and machine creation require a linked AMA account (the agent subsystem is
AMA-backed); the kanban board and tasks still work without AMA. Adds migrations
0025 (machines.hosting) and 0026 (agents.ama_agent_id).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each AK user connects their own AMA account via BetterAuth account-linking (AMA
registered as a generic OIDC provider, enabled only when AMA OAuth is configured).
AK→AMA calls now use that user's linked-account token (auto-refreshed via
getAccessToken), with ownerId threaded through the createAma* call graph, instead
of a faceless client_credentials token. Cloud-scheduling routes are gated on an
active AMA connection; standalone AK (no AMA env) stays a pure kanban, unchanged.
Self-hosted runners self-authenticate via ama-runner's native device login:
createAmaFederatedRunnerToken / signed-JWT / JWKS are removed, machine onboarding
returns only {origin, projectId, environmentId}, and `ak start` (unchanged usage)
spawns ama-runner with --api-server/--environment-id rather than minted tokens.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dispatch is the control plane's action on a task, so record it in task_actions as
a 'system' actor: 'dispatched' on success, 'dispatch_failed' on failure (deduped
by reason — one timeline entry per distinct error, not per retry). The reason
shows in the task detail Activity timeline; raw errors stay in logs. Adds the
'system' actor_type + the two actions (migration 0024) and ActivityLog rendering.
Bumps the bundled ama-runner to 0.3.4 (narrates lease lifecycle at INFO, stops
WARNing on normal lease end).
- Dispatch policy: defer blocked/scheduled/at-capacity tasks (todo+assigned,
no binding) instead of failing; 409 only when no machine supports the
runtime; skip runners whose provider quota window is at 100%.
- Per-minute cron closes the loop: reconcileAmaBoundTasks releases tasks
whose AMA session died and mops up leftover bindings; dispatchPendingAmaTasks
re-dispatches assigned, unblocked, due tasks; the stale sweep stops the
bound session before releasing.
- releaseTaskRuntimeBinding is the single teardown path: stop session,
revoke the vault session secret (credential id stored via migration 0023),
copy AMA usage totals into ama_agent_sessions, close the AK session, clear
annotations. Complete/cancel transition locally first with best-effort
teardown; reject falls back to restart when the session is already dead.
- PR state sync through a platform GitHub App: users install the app on
their repos (no per-user setup); POST /api/webhooks/github-app verifies
the app HMAC secret and maps PR merged→done / closed→cancelled in real
time, routed by pr_url — replacing the old 30s gh poll.
- Dispatch passes per-agent GIT_AUTHOR_*/GIT_COMMITTER_* in runtimeEnv;
machine registration returns a server-pinned runner version
(AMA_RUNNER_VERSION); AMA client retries once on 401 with a fresh token;
GET paths no longer provision AMA resources.
Per-machine QPS, error rate, and latency metrics powered by Cloudflare
Analytics Engine. Hono middleware writes data points per request,
AE SQL API aggregates over 5-minute windows. Admin machines page shows
all machines cross-tenant with owner, active agents, and live metrics.
Unify the app into a single Worker deployment:
- Move functions/api/ → server/, add worker/index.ts entry point
- Merge relay-worker DO (TunnelRelay) into the main worker
- Replace Pages Functions with @cloudflare/vite-plugin build
- Integrate share SSR (OG meta injection) into Hono routes
- Remove dayjs dep from shared (native Date suffices)
- Simplify dev to single `vite dev`, deploy via CF Git integration
- Delete packages/relay-worker/ and Pages-specific _routes.json
The getTask query derived active_session_id from the latest agent_session,
which returned the wrong session when an agent had multiple sessions.
Now session_id is stored directly on each task_action row and read from
the claimed action, giving the correct session for the chat relay.
Previously agents had two independent Ed25519 keys: one for JWT auth
(generated by crypto.subtle) and one for GPG commit signing (generated
by openpgp.js addSubkey). This violated the original design of one
cryptographic identity per agent.
Now the GPG subkey is the sole key source. Its Ed25519 material is
extracted as JWK for JWT auth. Agent ID = GPG subkey keyID. All agents
(including builtins) go through the GPG identity path.
Other changes:
- username is now required in CreateAgentInput (no more deriveUsername)
- username is globally unique (shared email domain), migration 0015
- Validation order: username check before GPG mutation to avoid orphans
- Runtime guards on openpgp.js internal field access
Complete agent commit identity implementation:
- Agent entity: add username (unique per owner, auto-derived from name)
and gpg_subkey_id fields. Email computed as username@mails.agent-kanban.dev.
- GPG subkey: auto-generated on agent creation via root key addSubkey().
Subkey ID stored on agent record for daemon use.
- Daemon injection: taskRunner sets up ephemeral GNUPGHOME with imported
root+subkey chain, injects GIT_AUTHOR/COMMITTER identity and GPG signing
config via GIT_CONFIG_COUNT env vars. Cleanup on process exit.
- API: GET /api/agents/:id/gpg-key (machine-only) returns armored private
key + subkey ID for daemon GPG setup.
- CLI: ak create agent --username flag, auto-derive if omitted.
- Session persistence: gpgSubkeyId, agentUsername, agentName saved for
resume flow.
Tasks can now specify a one-time scheduled_at timestamp (ISO 8601 with
timezone, normalized to UTC via dayjs). The daemon skips tasks whose
scheduled time has not yet arrived. Released/timed-out tasks have their
scheduled_at cleared so they become immediately re-dispatchable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Boards now have a required `type` field — `dev` for development workflows
(git/PR/code review) and `ops` for operations workflows (no repo). This
drives per-type system prompts, workspace creation (worktree vs tmpdir),
and task validation (dev requires repository_id, ops forbids it).
Key changes:
- Workspace abstraction (workspace.ts) decouples workspace lifecycle from
repo operations — scheduler and process manager stay unchanged
- System prompt generates per-board-type agent work protocol
- BoardType enum with isBoardType guard centralizes type validation
- gh auth downgraded to warning so ops-only daemons can run without it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add admin() plugin to betterAuth.ts with defaultRole "user" and adminRoles ["admin"]
- Create migration 0009_admin_fields.sql for role/banned/banReason/banExpires columns
- Restrict /api/auth/admin/* routes to user identity in auth.ts ROUTE_RULES
- Add 0008 and 0009 migrations to test helper applyMigrations list
- Document manual first-admin setup via D1 console in code comment
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a `seq` field to tasks, similar to GitHub issue numbers — each board
maintains its own counter. Displayed as #N on cards and detail drawer.
Title in detail drawer is now read-only (no EditableText).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Machines are now identified by a hardware fingerprint (sha256 of hostname + MAC),
eliminating duplicate machine creation on API key rotation or environment switching.
CLI config stores credentials per api-url host, enabling seamless environment switching
without re-entering keys.
- Add device.ts: generate deterministic device ID from hardware fingerprint
- Rewrite config.ts: credentials stored by host, auto-migrate legacy flat format
- POST /api/machines: upsert by (owner_id, device_id) via ON CONFLICT
- Heartbeat: bind unbound keys, reject keys bound to different machines
- Frontend reconnect: simplified to show `ak start --api-url` command only
- Migration 0006: add device_id column with NOT NULL + unique index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the task_notes table and agent_id/session_id columns with
task_actions and actor_type/actor_id to track all identity types
(user, machine, agent:worker, agent:leader). This enables user and
machine actions to appear in the activity log and board animations.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce agent `kind` field to differentiate worker agents (daemon-spawned)
from leader agents (CLI-launched via `ak claude/codex/gemini`). Leader agents
can complete, reject, cancel, and assign tasks; workers can only claim and
review. Session capabilities are granted based on kind.
- Add `kind` column to agents table (worker | leader)
- Split IdentityType: agent → agent:worker / agent:leader
- Runtime auto-detection via env vars (CLAUDECODE, CODEX_CI, GEMINI_CLI)
- `ak claude/codex/gemini` wraps runtime with session lifecycle
- `ak whoami` for agents to query their own identity
- JWT `aid` claim verified against session binding (prevent escalation)
- Auth middleware resolves kind in single JOIN query (no N+1)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prettier's aggressive line-splitting (printWidth 100, single quotes) didn't
match the project's existing compact code style. Switch to Biome which
provides formatting + linting in one tool with better chain-call handling.
- Remove prettier, add @biomejs/biome
- Configure biome: double quotes, lineWidth 150, recommended lint rules
- Update lefthook pre-commit to use `biome check`
- Full codebase format pass with new config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all human task management from UI — task creation (+ Task button,
onboarding Create Task step), status transitions (claim/cancel/release/assign),
delete button, and AssignDropdown component.
Board is now observation-only. Only reject and complete buttons remain,
shown exclusively for in_review tasks. Onboarding simplified from 3 to 2 steps.
Update CLAUDE.md with UI Principles and Post-Write Workflow to include
Playwright tests for frontend changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Coverage improved from 36.73% to 47.33%. Four new test files with 115 test
cases covering board CRUD, repository CRUD, all HTTP routes, and SSE streaming.
Shared test infrastructure extracted to tests/helpers/db.ts. Updated CLAUDE.md
with test ownership rule: main agent owns source code, test-writer agent owns
test code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>