Compare commits

...

3 Commits

Author SHA1 Message Date
Claude 10292af76f
docs: add detailed design document for bot users feature
Covers data model (is_bot flag + bot_owners table), API endpoints,
auth guardrails, notification skipping, frontend badge, and config flag.

https://claude.ai/code/session_01CL89RAEskas7Hz1TSr7eKR
2026-03-22 14:10:51 +00:00
Claude ce7956e842
docs: redesign agent integration around bot users as first-class citizens
Reworked the exploration plan so that assigning an agent feels like
assigning a coworker. Bot users appear in the regular assignee picker,
progress shows as normal task comments, and completion is just the bot
marking the task done. No special agent panels or new UI paradigms.

https://claude.ai/code/session_01CL89RAEskas7Hz1TSr7eKR
2026-03-22 11:08:50 +00:00
Claude 55f0322808
docs: add exploration plan for OpenClaw/NanoClaw integration
Analyzes how AI agent frameworks (OpenClaw/NanoClaw) could be integrated
with Vikunja to allow assigning agents to tasks for autonomous completion.

https://claude.ai/code/session_01CL89RAEskas7Hz1TSr7eKR
2026-03-22 11:02:44 +00:00
2 changed files with 832 additions and 0 deletions

477
plans/design-bot-users.md Normal file
View File

@ -0,0 +1,477 @@
# Design: Bot Users
## Overview
Bot users are first-class users with an `is_bot` flag. They appear alongside human users in assignee pickers, comment threads, and everywhere else users show up. The only visual difference is a small "Bot" badge next to their name.
Bot users are always owned by a human user who created them. They cannot log in via username/password — they only interact via the API using tokens. The owning user manages the bot's API tokens.
The feature is gated behind a config flag `service.enablebotusers` (default: `false`).
---
## Data Model
### User table changes
Add one column to the existing `users` table:
```go
// In pkg/user/user.go, add to User struct:
IsBot bool `xorm:"bool default false index" json:"is_bot"`
```
This is the only schema change to `users`. Bot users are regular rows in the `users` table with `is_bot = true`.
Bot users:
- Have a `username` (required, unique as usual)
- Have a `name` (optional display name)
- Have **no password** (empty string, never hashed)
- Have **no email** (empty string — no notifications needed)
- Use `Issuer = "local"` — but we skip the password/email validation for bots (see below)
- Have `Status = StatusActive`
- Have `EmailRemindersEnabled = false`
- Have `OverdueTasksRemindersEnabled = false`
### New table: `bot_owners`
Tracks which human user owns which bot:
```go
// New file: pkg/models/bot_users.go
type BotOwner struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"`
BotID int64 `xorm:"bigint not null unique index" json:"bot_id"` // FK → users.id (the bot)
OwnerID int64 `xorm:"bigint not null index" json:"owner_id"` // FK → users.id (the human)
Created time.Time `xorm:"created not null" json:"created"`
}
func (*BotOwner) TableName() string {
return "bot_owners"
}
```
**Why a separate table instead of a field on `User`?**
- Keeps the User struct clean — most code doesn't need to know about bot ownership
- Allows querying "all bots owned by user X" efficiently
- Avoids a self-referential FK on the users table
---
## Config
Add to `pkg/config/config.go`:
```go
ServiceEnableBotUsers Key = `service.enablebotusers`
```
Default: `false`. Set in `InitDefaultConfig()`:
```go
ServiceEnableBotUsers.setDefault(false)
```
Add to `config-raw.json` under the `service` section:
```json
{
"key": "enablebotusers",
"default_value": "false",
"comment": "If enabled, users can create bot users that interact with Vikunja via the API only."
}
```
Expose in the `/info` endpoint so the frontend knows whether to show bot-related UI:
```go
// In vikunjaInfos struct:
BotUsersEnabled bool `json:"bot_users_enabled"`
// In Info handler:
info.BotUsersEnabled = config.ServiceEnableBotUsers.GetBool()
```
---
## API Endpoints
All bot endpoints require `service.enablebotusers` to be `true`. Return `403` (or a specific error) if disabled.
### Create Bot
**`PUT /api/v1/user/bots`**
Request body:
```json
{
"username": "my-review-bot",
"name": "Code Review Bot"
}
```
Handler logic:
1. Check `config.ServiceEnableBotUsers.GetBool()` — return error if disabled
2. Validate username (same rules: 1-250 chars, no spaces, not reserved)
3. Create user with `IsBot = true`, no password, no email, `Status = Active`
4. Skip email confirmation flow entirely
5. Don't create an initial project for the bot (bots get added to projects manually)
6. Insert `BotOwner{BotID: newUser.ID, OwnerID: currentUser.ID}`
7. Return the created bot user
Response:
```json
{
"id": 42,
"username": "my-review-bot",
"name": "Code Review Bot",
"is_bot": true,
"created": "2026-03-22T...",
"updated": "2026-03-22T..."
}
```
### List My Bots
**`GET /api/v1/user/bots`**
Returns all bots owned by the current user. Supports search via `?s=` query param.
Handler logic:
1. Query `bot_owners WHERE owner_id = currentUser.ID`
2. Join with `users` to get bot details
3. Return list of bot users
### Get Bot
**`GET /api/v1/user/bots/:bot`**
Returns a single bot by ID. Must be owned by current user.
### Update Bot
**`POST /api/v1/user/bots/:bot`**
Update bot's `name` or `username`. Must be owned by current user.
Request body:
```json
{
"name": "New Bot Name"
}
```
### Delete Bot
**`DELETE /api/v1/user/bots/:bot`**
Soft-deletes (sets `Status = StatusDisabled`) or hard-deletes the bot user. Must be owned by current user.
Considerations:
- What happens to tasks the bot is assigned to? → Unassign the bot from all tasks on deletion.
- What about comments the bot posted? → Keep them (same as when a human user is deleted).
### Create API Token for Bot
**`PUT /api/v1/user/bots/:bot/tokens`**
Creates an API token owned by the bot user, but only the owning human can call this endpoint.
Handler logic:
1. Verify current user owns this bot (check `bot_owners`)
2. Create an `APIToken` with `OwnerID = bot.ID`
3. Return the token (visible only once, same as regular token creation)
Request body:
```json
{
"title": "Main bot token",
"permissions": {
"tasks": ["read_all", "update"],
"task_comments": ["read_all", "create"]
},
"expires_at": "2027-01-01T00:00:00Z"
}
```
### List Bot's API Tokens
**`GET /api/v1/user/bots/:bot/tokens`**
Lists API tokens belonging to the bot. Only the owning human can view these.
### Delete Bot's API Token
**`DELETE /api/v1/user/bots/:bot/tokens/:token`**
Deletes an API token belonging to the bot. Only the owning human can call this.
---
## Auth Changes
### Block password login for bots
In `pkg/routes/api/v1/login.go`, after retrieving the user:
```go
if user.IsBot {
return &user2.ErrAccountIsBot{UserID: user.ID}
}
```
Also in `CheckUserCredentials` (`pkg/user/user.go`) — bots have no password, so `bcrypt.CompareHashAndPassword` would fail naturally. But adding an explicit check is clearer and avoids the timing cost.
### Block CalDAV auth for bots
In `pkg/routes/caldav/auth.go`, reject bot users.
### API token auth works as-is
The existing API token middleware (`pkg/routes/api_tokens.go`) resolves the token owner and puts them in context. If the token belongs to a bot user, the bot user becomes the authenticated user. No changes needed here — the bot acts as itself.
### User creation validation bypass for bots
`checkIfUserIsValid()` in `pkg/user/user_create.go` currently requires password + username for local users and email for all. For bots:
- Skip password requirement
- Skip email requirement
- Still require username
This could be handled by either:
- **Option A**: Adding a bot-specific code path in `checkIfUserIsValid()` that checks `IsBot`
- **Option B**: Creating a separate `CreateBotUser()` function that skips those checks
**Recommendation: Option B** — a separate `CreateBotUser()` function in `pkg/user/` that handles bot-specific creation logic cleanly without polluting the existing user creation path. It would:
1. Validate username only
2. Check uniqueness (same as regular users)
3. Set `IsBot = true`, `Status = Active`, `EmailRemindersEnabled = false`, `OverdueTasksRemindersEnabled = false`
4. Insert user
5. Skip email confirmation, skip initial project creation
6. Dispatch `CreatedEvent` as usual
---
## Notification Changes
### Skip all email notifications for bots
In `User.ShouldNotify()` (`pkg/user/user.go`):
```go
func (u *User) ShouldNotify(sessions ...*xorm.Session) (bool, error) {
// ... existing session setup ...
user, err := getUser(s, &User{ID: u.ID}, true)
if err != nil {
return false, err
}
if user.IsBot {
return false, nil
}
return user.Status != StatusDisabled && user.Status != StatusAccountLocked, err
}
```
This is comprehensive — it prevents all notification types (email, overdue reminders, etc.) for bot users.
---
## User Search / Assignee Picker Changes
### Bots appear in user search
The `ListUsers()` function in `pkg/user/users_project.go` searches by username/name. Bot users will naturally appear in search results since they're regular users. No backend changes needed.
### Bots appear in project member lists
Bot users can be added to projects via the existing project user/team mechanisms. Once added, they appear in the assignee picker for that project's tasks. No changes needed to the project membership system.
---
## Frontend Changes
### IUser type
Add `isBot` to `frontend/src/modelTypes/IUser.ts`:
```typescript
export interface IUser extends IAbstract {
// ... existing fields ...
isBot: boolean
}
```
### User model
Add default in `frontend/src/models/user.ts`:
```typescript
isBot = false
```
### User.vue component — Bot badge
Add a "Bot" badge next to the username when `user.isBot` is true:
```vue
<template>
<div class="user" :class="{'is-inline': isInline}">
<img
v-tooltip="displayName"
:height="avatarSize"
:src="avatarSrc"
:width="avatarSize"
:alt="'Avatar of ' + displayName"
class="avatar"
>
<span v-if="showUsername" class="username">{{ displayName }}</span>
<span v-if="user.isBot" class="bot-badge">Bot</span>
</div>
</template>
```
Style the badge as a small pill/tag:
```scss
.bot-badge {
display: inline-flex;
align-items: center;
font-size: 0.65rem;
font-weight: 600;
padding: 0.1rem 0.35rem;
border-radius: 0.25rem;
background: var(--grey-200);
color: var(--grey-600);
margin-inline-start: 0.25rem;
vertical-align: middle;
text-transform: uppercase;
letter-spacing: 0.02em;
}
```
This badge appears everywhere the `User` component is used: assignee lists, comment headers, task detail sidebar, etc.
### Info store
Add `botUsersEnabled` to the frontend's info/config store so components know whether to show bot-related UI (e.g., the "Manage Bots" settings page).
### Bot management settings page
New page at `frontend/src/views/user/settings/Bots.vue`:
- List bots owned by current user
- Create new bot (form: username, name)
- Edit bot (update name)
- Delete bot (with confirmation)
- Manage bot API tokens:
- Create token (show once, copy to clipboard)
- List existing tokens
- Delete token
This page only appears in the settings sidebar when `botUsersEnabled` is `true`.
---
## Database Migration
Single migration adding:
1. `is_bot` column to `users` table (bool, default false, indexed)
2. `bot_owners` table
```go
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "<timestamp>",
Description: "Add bot user support",
Migrate: func(tx *xorm.Engine) error {
// 1. Add is_bot column
err := tx.Exec("ALTER TABLE users ADD COLUMN is_bot BOOLEAN NOT NULL DEFAULT FALSE")
if err != nil { return err }
err = tx.Exec("CREATE INDEX IDX_users_is_bot ON users (is_bot)")
if err != nil { return err }
// 2. Create bot_owners table
type BotOwner struct {
ID int64 `xorm:"bigint autoincr not null unique pk"`
BotID int64 `xorm:"bigint not null unique index"`
OwnerID int64 `xorm:"bigint not null index"`
Created time.Time `xorm:"created not null"`
}
return tx.Sync2(&BotOwner{})
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}
```
---
## Error Types
New error types in `pkg/user/error.go` (or `pkg/models/error.go`):
| Error | Code | When |
|-------|------|------|
| `ErrAccountIsBot` | TBD | Bot tries to log in with password |
| `ErrBotUsersDisabled` | TBD | Bot endpoint called when feature is off |
| `ErrBotNotOwned` | TBD | User tries to manage a bot they don't own |
| `ErrCannotMakeBotOwner` | TBD | Trying to set a bot as owner of another bot |
---
## What's NOT in scope (for now)
These are deferred to the agent dispatch phase:
- Agent endpoint configuration (OpenClaw/NanoClaw connection details)
- Auto-dispatching work when a bot is assigned to a task
- Agent status tracking
- Health monitoring / timeout cron jobs
- Bot-specific avatar generation (bots use the default avatar provider for now)
---
## Files to Create or Modify
### New Files
| File | Purpose |
|------|---------|
| `pkg/models/bot_users.go` | BotOwner model, bot CRUD logic |
| `pkg/models/bot_users_permissions.go` | Permission checks for bot operations |
| `pkg/routes/api/v1/bots.go` | Bot API endpoint handlers |
| `pkg/migration/<timestamp>.go` | Database migration |
| `frontend/src/views/user/settings/Bots.vue` | Bot management UI |
| `frontend/src/services/botUser.ts` | Bot API service |
### Modified Files
| File | Change |
|------|--------|
| `pkg/user/user.go` | Add `IsBot` field to `User` struct |
| `pkg/user/user.go` | Update `ShouldNotify()` to return false for bots |
| `pkg/user/user_create.go` | Add `CreateBotUser()` function |
| `pkg/config/config.go` | Add `ServiceEnableBotUsers` config key |
| `config-raw.json` | Add config schema entry |
| `pkg/routes/api/v1/info.go` | Expose `BotUsersEnabled` in `/info` |
| `pkg/routes/api/v1/login.go` | Block bot login |
| `pkg/routes/routes.go` | Register bot API routes |
| `frontend/src/modelTypes/IUser.ts` | Add `isBot` field |
| `frontend/src/models/user.ts` | Add `isBot` default |
| `frontend/src/components/misc/User.vue` | Add bot badge |
| `frontend/src/i18n/lang/en.json` | Add translation strings |
---
## Open Questions
1. **Should bots be sharable?** Can user A transfer ownership of a bot to user B? Or share management access? For now: no. One owner per bot, non-transferable.
2. **Hard delete vs soft delete?** When a bot is deleted, should we hard-delete the user row or set `Status = Disabled`? Soft delete (disabled) is safer since it preserves audit trail and comment attribution. Recommend soft delete.
3. **Bot-to-bot ownership?** A bot should not be able to own another bot. The owner must be a human user (`is_bot = false`). Enforce in the creation handler.
4. **Bot API token scopes — should we restrict?** Should bots be able to have tokens with any permission, or only a subset? For now: same as regular users — any valid permission scope. The owning user decides what the bot can do.
5. **Rate limiting for bot API calls?** Not in this phase. Can be added as a config option later (`service.botratelimit`).

View File

@ -0,0 +1,355 @@
# Exploring OpenClaw/NanoClaw Integration with Vikunja
## What Are OpenClaw and NanoClaw?
**OpenClaw** is an open-source AI agent framework (the fastest-growing GitHub project in history as of early 2026). It connects to messaging apps and enables LLMs to autonomously perform tasks. It exposes an HTTP Agent API with endpoints like `/api/agent/createTask`, `/api/agent/updateTask`, etc., and has 100+ preconfigured "AgentSkills."
**NanoClaw** is a lightweight, security-focused alternative built on Anthropic's Claude Agent SDK. It prioritizes containerized isolation and radical simplicity (~500 lines of core code vs OpenClaw's ~500k). It uses a task-scheduler with cron/interval scheduling and isolated container sandboxes per group.
Both frameworks can connect to external services, execute code, and manage tasks autonomously.
---
## Design Principle
**Assigning an agent to a task should feel exactly like assigning a coworker.**
- Bot users appear in the same assignee picker as human users
- You assign them the same way — search, click, done
- Progress shows up as regular comments in the task thread
- The task gets marked done when the bot finishes
- The only visual difference is a small bot badge next to the name
No separate "agent panels," no special buttons. The existing assignee UX *is* the interface.
---
## Integration Concept
The core idea: **assign an AI agent (via OpenClaw or NanoClaw) to a Vikunja task, and the agent autonomously works to complete it.**
This requires two directions of communication:
1. **Vikunja → Agent**: Assigning a bot user to a task triggers a dispatch to the agent's endpoint
2. **Agent → Vikunja**: The agent calls back via the standard Vikunja API to post comments, update the task, attach files, and mark it done
---
## What Already Exists in Vikunja That Helps
### Task Assignees (the primary UX surface)
- Tasks can have multiple assignees (users)
- Assignment dispatches `TaskAssigneeCreatedEvent`
- Existing assignee picker UI with search
- **This becomes the integration trigger** — assigning a bot user dispatches work to the agent
### REST API + API Tokens (inbound control)
- Full CRUD API for tasks, comments, attachments, assignees
- Scoped API tokens (`tk_` prefix) with per-route permissions
- **This handles Agent → Vikunja updates** — the bot user gets its own API token
### Event System (Watermill-based)
- Async event dispatch with retry logic
- Existing task lifecycle events (created, updated, deleted, assignee changes)
- Listener registration pattern for extending behavior
- **New listener on `TaskAssigneeCreatedEvent`** checks if assignee is a bot and dispatches
### Cron System
- `robfig/cron/v3` for scheduled background work
- Used for reminders, overdue checks, cleanup jobs
- **Can monitor running agent tasks** for timeouts/failures
### Webhooks (outbound notifications)
- Project-level and user-level webhooks already exist
- HMAC-SHA256 signing, Basic Auth support
- Could supplement the integration but not the primary mechanism
---
## What Would Need to Change
### 1. Bot User Identity (`pkg/user/user.go`)
Add `IsBot` flag to the User struct:
```go
IsBot bool `xorm:"bool default false" json:"is_bot"`
```
Bot users are real users with a special flag. They:
- Appear in the assignee picker alongside regular users
- Show a bot badge in the UI (avatar, comments, assignee lists)
- Cannot log in via the web UI — API token only
- Don't receive email notifications (reminders, overdue, etc.)
- Don't need email confirmation, password, or TOTP
- Have their own audit trail — every action is attributed to the bot
**Database migration:** Add `is_bot` column to `users` table.
### 2. Agent Connection Config on Bot Users (`pkg/user/user.go` or new model)
Bot users need agent connection details. Two approaches:
**Option A: Fields on the User model** (simpler)
```go
AgentEndpointURL string `xorm:"text null" json:"agent_endpoint_url,omitempty"`
AgentAPIKey string `xorm:"text null" json:"-"` // never exposed in API responses
AgentType string `xorm:"varchar(50) null" json:"agent_type,omitempty"` // "openclaw", "nanoclaw"
```
**Option B: Separate `bot_configs` table** (cleaner separation)
```go
type BotConfig struct {
ID int64
UserID int64 // FK to users, unique (one config per bot user)
EndpointURL string
APIKey string // encrypted at rest
Type string // "openclaw", "nanoclaw"
Created time.Time
Updated time.Time
}
```
**Recommendation: Option B** — keeps the User model clean, allows the config to be managed independently, and makes it easier to encrypt the API key at rest.
### 3. Agent Dispatch Listener (`pkg/models/listeners.go`)
New listener on `TaskAssigneeCreatedEvent`:
```
When a bot user is assigned to a task:
1. Load the bot's agent config (endpoint URL, API key, type)
2. Load full task context (title, description, comments, attachments, related tasks)
3. Generate a scoped API token for the bot user (if one doesn't exist)
4. POST to the agent endpoint with task details + callback token
5. The agent starts working autonomously
```
Corresponding listener on `TaskAssigneeDeletedEvent`:
```
When a bot user is unassigned from a task:
1. POST a cancellation request to the agent endpoint
2. Agent stops work on that task
```
### 4. Agent Adapter Interface (`pkg/modules/agents/`)
Generic interface with provider-specific implementations:
```go
type AgentProvider interface {
// Dispatch sends a task to the agent for processing
Dispatch(ctx context.Context, task *models.Task, callbackURL string, callbackToken string) error
// Cancel tells the agent to stop working on a task
Cancel(ctx context.Context, taskID int64) error
// Status checks if the agent is healthy
Status(ctx context.Context) (AgentStatus, error)
}
```
Implementations:
- `pkg/modules/agents/openclaw/` — OpenClaw HTTP API adapter
- `pkg/modules/agents/nanoclaw/` — NanoClaw adapter
### 5. Bot User Creation API
New endpoint or flag on existing user creation:
- `PUT /api/v1/bots` — create a bot user (admin or project admin only)
- Takes: name, username, agent type, endpoint URL, API key
- Creates user with `is_bot: true`
- Creates associated `BotConfig`
- Auto-generates an API token for the bot
- Returns the bot user + token (token shown only once)
- `GET /api/v1/bots` — list bot users accessible to current user
- `POST /api/v1/bots/{id}` — update bot config
- `DELETE /api/v1/bots/{id}` — deactivate bot user
### 6. Frontend Changes
**Minimal — that's the point.** The UX piggybacks on existing patterns:
#### Assignee Picker (modify existing)
- Bot users already appear in the user list (they're real users)
- Add a small bot icon/badge next to bot user names
- No other changes needed — assignment works identically
#### User/Avatar Display (`frontend/src/components/misc/User.vue`)
- Show bot badge (small robot icon) on avatar
- Applied everywhere users are displayed: assignee lists, comments, activity log
#### Task Comments
- Comments from bot users get a subtle "bot" indicator
- No separate rendering — same comment thread, same layout
- Users reply to bot comments naturally (the agent sees replies via API polling or webhooks)
#### Bot Management UI (new, minimal)
- Settings page for creating/managing bot users
- Form: name, username, agent type, endpoint URL, API key
- Shows the generated API token once on creation
- Could live under team/workspace settings
#### Model Types (`frontend/src/modelTypes/IUser.ts`)
- Add `isBot: boolean` to `IUser`
- Add `agentType?: string` to `IUser`
### 7. Guard Rails & Edge Cases
**Authentication:**
- Bot users cannot authenticate via username/password or OIDC
- Only API token auth is valid for bots
- Login endpoint rejects users with `is_bot: true`
**Notifications:**
- Skip email notifications for bot users (reminders, overdue, mentions)
- Bot users shouldn't trigger "user mentioned" notifications when they @-mention someone? Or should they? (configurable)
**Permissions:**
- Bot users need project access just like regular users (added to project/team)
- Bot actions are scoped by the same permission system
- Bot can only modify tasks in projects it has write access to
**Rate Limiting:**
- Consider rate limits on bot API calls to prevent runaway agents
- Configurable per bot or globally
**Failure Handling:**
- Cron job checks for stalled agent tasks (assigned to bot, no activity for X minutes)
- Notify the user who assigned the bot if the agent appears stuck
- Auto-unassign after configurable timeout
---
## The User Experience, End to End
### Setup (one-time)
1. Admin goes to Settings → Bots
2. Creates a bot: "CodeReview Bot", type: OpenClaw, endpoint: `https://openclaw.example.com/api/agent`
3. Vikunja creates a bot user, generates an API token, shows it once
4. Admin adds the bot user to the relevant project(s)/team(s)
### Daily Use
1. User creates a task: "Review PR #42 for security issues"
2. User clicks the assignee picker, sees both coworkers and bots
3. User assigns "CodeReview Bot" — looks just like assigning a coworker
4. Behind the scenes: Vikunja dispatches the task to OpenClaw
5. Minutes later, comments start appearing on the task from the bot:
- "Started reviewing PR #42..."
- "Found 2 potential issues: SQL injection in `user_query.go:45`, missing auth check in `api/handler.go:112`"
- "Full report attached."
6. Bot attaches a detailed report file
7. Bot marks the task as done
8. User sees it all in the normal task detail view — no special UI needed
### Collaboration
- User can reply to bot comments with clarifications
- User can unassign the bot to stop it
- User can assign a different bot or a human to take over
- Multiple bots can be assigned (one researches, another implements)
---
## Implementation Phases
### Phase 1: Bot User Foundation
- Add `is_bot` field to User model + migration
- Add `BotConfig` model + migration
- Bot creation API endpoint
- Skip email/password/TOTP requirements for bots
- Block web login for bots
- Skip email notifications for bots
- Frontend: `isBot` on IUser, bot badge on User.vue
**~8-12 files changed**
### Phase 2: Agent Dispatch
- Agent provider interface (`pkg/modules/agents/`)
- OpenClaw adapter
- NanoClaw adapter
- Listener on `TaskAssigneeCreatedEvent` to dispatch to agent
- Listener on `TaskAssigneeDeletedEvent` to cancel agent work
- Scoped API token auto-generation for bots
**~6-10 new files**
### Phase 3: Monitoring & Resilience
- Cron job for stalled agent detection
- Notify assigning user on agent failure
- Agent health check endpoint
- Rate limiting for bot API calls
**~3-5 files**
### Phase 4: Polish
- Bot management UI in frontend settings
- Bot comment styling
- Configuration for agent dispatch behavior (what context to send, timeouts)
- Documentation
**~4-6 files**
---
## Suggested File Structure
```
pkg/
├── user/
│ └── user.go # + IsBot field
├── models/
│ ├── bot_config.go # BotConfig model (endpoint, API key, type)
│ ├── bot_config_permissions.go # BotConfig permissions
│ ├── events.go # + BotAssignedToTaskEvent
│ └── listeners.go # + bot dispatch listener
├── modules/
│ └── agents/
│ ├── agent.go # AgentProvider interface
│ ├── dispatch.go # Dispatch logic (called by listener)
│ ├── openclaw/
│ │ └── openclaw.go # OpenClaw adapter
│ └── nanoclaw/
│ └── nanoclaw.go # NanoClaw adapter
├── routes/
│ └── api/v1/
│ └── bots.go # Bot CRUD endpoints
frontend/src/
├── modelTypes/
│ └── IUser.ts # + isBot, agentType
├── models/
│ └── user.ts # + isBot default
├── components/
│ └── misc/
│ └── User.vue # + bot badge
├── views/
│ └── settings/
│ └── Bots.vue # Bot management page
```
---
## Key Design Decisions
| Decision | Recommendation | Rationale |
|----------|---------------|-----------|
| Bot identity | `is_bot` flag on User | Bots are first-class citizens, appear in all user contexts naturally |
| Agent config storage | Separate `BotConfig` table | Clean separation, easier to encrypt API keys |
| UX surface | Existing assignee picker | "Assign work to a coworker" feeling — no new concepts to learn |
| Dispatch trigger | `TaskAssigneeCreatedEvent` listener | Piggybacks on existing event system, zero new UI needed |
| Agent communication | Standard Vikunja REST API | Bot uses same API as any other client — comments, updates, attachments |
| Provider support | Adapter pattern with interface | Support OpenClaw and NanoClaw (and future providers) cleanly |
| Progress reporting | Task comments | Shows up naturally in the existing task detail view |
| Completion | Bot marks task done via API | Same as a human marking it done — no special mechanism |
---
## Summary
The integration is built on one core insight: **bot users are users.** By adding an `is_bot` flag and wiring up agent dispatch on assignment, the entire existing Vikunja UX — assignee picker, comments, task status, attachments — becomes the agent interface. No new UI paradigms needed.
The new code is focused on:
1. Bot user identity (`is_bot` flag + `BotConfig`)
2. Agent dispatch (listener + provider adapters)
3. Visual distinction (bot badge in frontend)
Everything else — permissions, API, events, comments, task lifecycle — already works.