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
| Decision | Why | Trade-off accepted | Record |
|---|---|---|---|
| Use PostgreSQL semantics everywhere, with embedded PGlite locally and PostgreSQL in production, through MikroORM | Local 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 run | Database and ORM |
| Keep an append-only event ledger, read from projections, and write side effects through a transactional outbox | Task state arrives through streams, webhooks and polling, with duplicates and gaps. One idempotent path plus a replayable ledger makes the result correct and rebuildable | More storage, and raw events can contain sensitive data. Workers need leases, retries and visibility | Ledger, projections, outbox |
| Give every durable thing a local UUID, and identify a remote task by agent, tenant and remote task ID | Two agents can issue the same ID. URLs and permission checks must not depend on strings a remote party controls | Cross-agent links need local relations. The design calls for them, and no link table exists yet | Local identity for remote resources |
| One codebase and one domain model, with a stateless web tier and separate worker entry points, not microservices | Long-running work must not depend on browser connections or request lifetimes. A network split would add cost before the domain is stable | Module boundaries are enforced by convention in one repo. Web and workers share one database | Modular 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 state | Conflating them can leak credentials or let a state change act as an authorization | More concepts and more UI wording. Credentials never travel in messages | Authentication 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 rows | Files are written before the database commit, so a failed transaction can leave an unreferenced file. No garbage collection yet | Initial 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 profile | Accepted work survives a closed browser or restart. Resending after an unknown failure could duplicate real-world actions | A 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 process | Durable commands and uncertain outcomes |
| Workers own the task streams, and the browser reads committed state | Observation continues with zero browsers open. A reconnect observes and never resends | Eight concurrent streams per worker, so scale means more workers. Some identical append chunks cannot be told from replays | Worker-owned subscriptions |
| Opt-in push webhooks with a per-task credential derived from a server-only key | Push gets changes without polling and does not store secrets in rows | Needs a public HTTPS callback origin and a shared key. Rotating the key invalidates existing callbacks | Authenticated task push |
Poll known tasks with GetTask every 15 seconds and sweep ListTasks, with durable cursors. Never resend a command | Convergence even if streams and webhooks fail | Extra read traffic. Reconciliation only updates tasks the console already knows | Task reconciliation |
| Versioned projections that can be rebuilt atomically from the ledger | Fix projection bugs or add fields without losing history, and without touching agents | Needs retained events. Rebuild reduces a whole task's ledger each time, favoring correctness over speed | Versioned projections and rebuild |
| Push content-free freshness signals over SSE, and make the browser re-query | A lost or duplicate signal cannot corrupt anything. No replay log to maintain | Up to a few seconds of staleness if a signal is lost. Each signal costs a re-read | Freshness signals |
| Production login through OIDC with membership provisioned by an operator, opaque server-side sessions, and no tokens kept | Provider claims cannot create or promote users. A stolen cookie can be revoked server-side | No auto-provisioning. Membership management is a CLI plus the admin page. No API tokens | Sessions and membership |
| Grants per agent and skill, an encrypted credential vault, an outbound network policy, pinned card signing keys, and a skill-routing extension | A skill label is not a security boundary. Credentials stay on the server. Agents are reached only at approved addresses | gRPC is refused until it can be made safe. User-delegated OAuth is not built (#1). Operators must protect keys and the database | Service credentials and scoped security |
| Approvals are typed, immutable revisions with a digest. A decision names the exact revision, and execution commits in the same transaction | An approval must prove who authorized exactly what. It cannot be reused for different content | Only two action kinds exist. Plain replies can still bypass an open approval (#16). The console cannot prove what the agent then did | Decision aggregates |
| Keep task ownership, due times and escalation in the console, separate from remote state | Operators need accountability for work the agent knows nothing about | Escalation is one hop. Teams cannot be targets. Nothing is sent to the agent | Ownership and escalation |
| A read-only audit timeline built from the domain records themselves, with database-level immutability | The record alone answers who decided what and when, with no second copy to drift | Computed per request. Tamper evidence is immutability, not cryptographic. Retention needs a controlled maintenance path | Workflow audit trail |
| Durable per-person notifications, raised in the causing transaction, plus one optional signed webhook | Tell people reliably without putting delivery on the request path. The webhook is for team chat | One webhook per deployment. At-least-once delivery. No per-person preferences, email or retention limits yet | Durable notifications |
| One inbox query over existing indexed columns, with keyset paging, and no separate inbox table | Nothing extra to keep in sync, and pages have no gaps or repeats | Saved views, text search and bulk actions are not built (#5) | Unified inbox |
| Structured input forms through an advertised extension, using a small schema subset | Better input than free text, without letting agents inject UI or regular expressions | Anything beyond a flat form needs a new extension version | Structured input forms |
| An optional AG-UI adapter that is one durable command per run, off by default | Rich agent-UI runtimes can use A2A agents without a second, non-durable path | Frontend 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 data | Generated interfaces without running agent code or loading agent URLs | Only 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 it | Approval should start where the agent knows what it needs, without trusting the agent | A 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 headers | A findable, unclaimed name. Identifiers are permanent once agents advertise them | A rename would break webhook receivers and agents. Some identifiers still carry older names (#27). The URIs depend on one domain staying registered | Product 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 database | Try the product with one command and no clone | The package is large. Explicit demo mode relaxes the agent origin allowlist because it already grants an unauthenticated administrator. Multi-replica PostgreSQL keeps migrations manual | npm distribution and the launcher |
Proposed, not decided
| Decision | Why it was proposed | Trade-off | Record |
|---|---|---|---|
| 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 console | Needs service tokens, a stable versioned API and an events strategy first. None exist today. Nothing in the product changes until it is accepted | Client SDK |
Related
Created and maintained by Shashi Kanth G S at allsrc.dev. Visit shashikanth.me.
Rich content
For readers who want to know how Taskbay shows what agents send (text, data, files, forms, generated interfaces), and the firm rule behind all of it: agent content is data, never code.
Use the console: chat, tasks, flows, audit and settings
anyone who works in the Taskbay console day to day. You will have a map of every screen, know what each control does, and know which actions your role allows.