agent-taskbay
Concepts

Design decisions

For readers evaluating Taskbay who want to know why it is built this way, and what each choice costs. Each row links to the longer archived decision record.

> For readers evaluating Taskbay who want to know why it is built this way, and what each choice costs. Each row links to the longer archived decision record.

The records in docs/archive/adr/ were written for maintainers. They are kept unchanged and are the detailed source. This page restates each decision in plain language, with the trade-off accepted. Where a later decision changed an earlier one, the row says so.

Accepted decisions

DecisionWhyTrade-off acceptedRecord
Use PostgreSQL semantics everywhere, with embedded PGlite locally and PostgreSQL in production, through MikroORMLocal use needs no database server, yet behavior (JSON, constraints, transactions) matches production. Avoids "worked on SQLite, broke on Postgres"PGlite allows only one process to own a data directory. A SQLite adapter would be extra work, not a connection-string change. PostgreSQL tests must still runDatabase and ORM
Keep an append-only event ledger, read from projections, and write side effects through a transactional outboxTask state arrives through streams, webhooks and polling, with duplicates and gaps. One idempotent path plus a replayable ledger makes the result correct and rebuildableMore storage, and raw events can contain sensitive data. Workers need leases, retries and visibilityLedger, projections, outbox
Give every durable thing a local UUID, and identify a remote task by agent, tenant and remote task IDTwo agents can issue the same ID. URLs and permission checks must not depend on strings a remote party controlsCross-agent links need local relations. The design calls for them, and no link table exists yetLocal identity for remote resources
One codebase and one domain model, with a stateless web tier and separate worker entry points, not microservicesLong-running work must not depend on browser connections or request lifetimes. A network split would add cost before the domain is stableModule boundaries are enforced by convention in one repo. Web and workers share one databaseModular monolith
Keep three kinds of authentication apart: people sign in (Plane A), the console authenticates to agents (Plane B), and in-task AUTH_REQUIRED is only a task stateConflating them can leak credentials or let a state change act as an authorizationMore concepts and more UI wording. Credentials never travel in messagesAuthentication planes
Persist task content as a projection and archive binary parts by digest outside the database (the first version of this was later replaced by the versioned projections below)UI reads must not replay the whole ledger. Large binary data does not belong in relational rowsFiles are written before the database commit, so a failed transaction can leave an unreferenced file. No garbage collection yetInitial projections and archives
Save a command and an outbox row in one transaction, dispatch from a worker, and mark a failure after dispatch as uncertain instead of resending. Embedded dispatch for the local profileAccepted work survives a closed browser or restart. Resending after an unknown failure could duplicate real-world actionsA person must resolve uncertain commands. Local mode needs a long-running Node process. This replaced the earlier rule that local mode runs a separate worker processDurable commands and uncertain outcomes
Workers own the task streams, and the browser reads committed stateObservation continues with zero browsers open. A reconnect observes and never resendsEight concurrent streams per worker, so scale means more workers. Some identical append chunks cannot be told from replaysWorker-owned subscriptions
Opt-in push webhooks with a per-task credential derived from a server-only keyPush gets changes without polling and does not store secrets in rowsNeeds a public HTTPS callback origin and a shared key. Rotating the key invalidates existing callbacksAuthenticated task push
Poll known tasks with GetTask every 15 seconds and sweep ListTasks, with durable cursors. Never resend a commandConvergence even if streams and webhooks failExtra read traffic. Reconciliation only updates tasks the console already knowsTask reconciliation
Versioned projections that can be rebuilt atomically from the ledgerFix projection bugs or add fields without losing history, and without touching agentsNeeds retained events. Rebuild reduces a whole task's ledger each time, favoring correctness over speedVersioned projections and rebuild
Push content-free freshness signals over SSE, and make the browser re-queryA lost or duplicate signal cannot corrupt anything. No replay log to maintainUp to a few seconds of staleness if a signal is lost. Each signal costs a re-readFreshness signals
Production login through OIDC with membership provisioned by an operator, opaque server-side sessions, and no tokens keptProvider claims cannot create or promote users. A stolen cookie can be revoked server-sideNo auto-provisioning. Membership management is a CLI plus the admin page. No API tokensSessions and membership
Grants per agent and skill, an encrypted credential vault, an outbound network policy, pinned card signing keys, and a skill-routing extensionA skill label is not a security boundary. Credentials stay on the server. Agents are reached only at approved addressesgRPC is refused until it can be made safe. User-delegated OAuth is not built (#1). Operators must protect keys and the databaseService credentials and scoped security
Approvals are typed, immutable revisions with a digest. A decision names the exact revision, and execution commits in the same transactionAn approval must prove who authorized exactly what. It cannot be reused for different contentOnly two action kinds exist. Plain replies can still bypass an open approval (#16). The console cannot prove what the agent then didDecision aggregates
Keep task ownership, due times and escalation in the console, separate from remote stateOperators need accountability for work the agent knows nothing aboutEscalation is one hop. Teams cannot be targets. Nothing is sent to the agentOwnership and escalation
A read-only audit timeline built from the domain records themselves, with database-level immutabilityThe record alone answers who decided what and when, with no second copy to driftComputed per request. Tamper evidence is immutability, not cryptographic. Retention needs a controlled maintenance pathWorkflow audit trail
Durable per-person notifications, raised in the causing transaction, plus one optional signed webhookTell people reliably without putting delivery on the request path. The webhook is for team chatOne webhook per deployment. At-least-once delivery. No per-person preferences, email or retention limits yetDurable notifications
One inbox query over existing indexed columns, with keyset paging, and no separate inbox tableNothing extra to keep in sync, and pages have no gaps or repeatsSaved views, text search and bulk actions are not built (#5)Unified inbox
Structured input forms through an advertised extension, using a small schema subsetBetter input than free text, without letting agents inject UI or regular expressionsAnything beyond a flat form needs a new extension versionStructured input forms
An optional AG-UI adapter that is one durable command per run, off by defaultRich agent-UI runtimes can use A2A agents without a second, non-durable pathFrontend tools, state and non-text content are unsupported. Runs stream about 50 seconds. Same-origin clients only (#8 to #14)AG-UI adapter
Render A2UI from an allowlist of Basic Catalog components, as dataGenerated interfaces without running agent code or loading agent URLsOnly nine components. No images, media or links. Chat only (#22 to #25)A2UI renderer
Let an agent open an approval request through an extension, with no authority to decide itApproval should start where the agent knows what it needs, without trusting the agentA hostile agent can create a pending request that a person must reject. Reply-bypass and echo-contract problems are open (#16, #17)Agent-originated approvals
Name the product Agent Taskbay, and use the product slug in extension URIs, media types and headersA findable, unclaimed name. Identifiers are permanent once agents advertise themA rename would break webhook receivers and agents. Some identifiers still carry older names (#27). The URIs depend on one domain staying registeredProduct name
Publish one npm package with a launcher that runs the production build, bound to loopback, with state in one data directory, and auto-migrate for the embedded databaseTry the product with one command and no cloneThe package is large. Explicit demo mode relaxes the agent origin allowlist because it already grants an unauthenticated administrator. Multi-replica PostgreSQL keeps migrations manualnpm distribution and the launcher

Proposed, not decided

DecisionWhy it was proposedTrade-offRecord
A typed client SDK for the Taskbay HTTP API (status: proposed and parked, tracked in #31)Other applications might want durable tasks, retries and approvals without writing a consoleNeeds service tokens, a stable versioned API and an events strategy first. None exist today. Nothing in the product changes until it is acceptedClient SDK

Created and maintained by Shashi Kanth G S at allsrc.dev. Visit shashikanth.me.

On this page