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.
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>
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
Task endpoints (list, get, update, delete, lifecycle, messages, notes, SSE)
were missing owner_id scoping — any authenticated caller could see and mutate
tasks belonging to other tenants. Add Hono middleware on /api/tasks/:id routes
to assertTaskOwner via board ownership, and thread ownerId through listTasks
and getTask queries.
Also fix two daemon-affecting issues:
- agentAuth /agent/session rate limit raised from 60 to 600 req/min (daemon
with 3 concurrent agents was hitting the default limit)
- auth middleware now transparently passes 429 from Better Auth instead of
wrapping it as 401, so CLI backoff logic works correctly
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 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>
Make agent runtime a required field across the full stack — DB, API,
shared types, CLI, and frontend. Define AgentRuntime enum ("claude" |
"codex" | "gemini") as single source of truth with labels. Remove
defaultProvider from daemon since runtime is now per-agent. Enrich
ak start/stop/status output with structured daemon state.
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>
JSON fields (Agent.skills, Agent.handoff_to, Task.labels, Task.input)
were leaking as raw strings in API responses. Now deserialized at the
repo layer boundary via a shared parseJsonFields helper in db.ts.
Also installs each agent's own skills (from agent.skills field) in the
daemon before spawning, alongside the shared agent-kanban skill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>