Docs

Lexicon Markup Language

A simple markup language for defining AT Protocol lexicons. Write human-readable field definitions, and Cards will generate both the AT Protocol lexicon JSON and Python dataclasses.

Quick Start

name: Task Card
description: Track tasks with priority and due dates

fields:
  title: string required max:300
  details: text max:10000
  priority: enum(low, medium, high, critical) default:medium
  due_date: datetime
  tags: array:string max:10
  completed: boolean default:false

Syntax

Header Section

Field Required Description
name: Yes Human-readable name for the card type
description: No Description of what this card type is for

Fields Section

Start with fields: then indent each field definition:

fields:
  field_name: type [modifiers]

Available Types

Type Description AT Protocol Type
string Single line text string
text Multi-line text (renders as textarea) string
boolean True/false boolean
integer Whole number integer
datetime ISO 8601 date/time string with format: datetime
uri URL/URI string with format: uri
did AT Protocol DID string with format: did
handle AT Protocol handle string with format: handle
enum(a,b,c) Enumerated values string with knownValues
array:type List of items array with item type

Modifiers

Modifier Description Example
required Field must have a value title: string required
max:N Maximum length (strings) or items (arrays) title: string max:300
default:value Default value status: enum(a,b) default:a

Examples

Simple Note

name: Note
description: Quick notes and thoughts

fields:
  content: text required max:5000
  pinned: boolean default:false

Project Task

name: Project Task
description: Task with assignee and dependencies

fields:
  title: string required max:200
  description: text max:5000
  assignee: handle
  status: enum(todo, in_progress, blocked, done) default:todo
  priority: enum(low, medium, high, urgent) default:medium
  due_date: datetime
  tags: array:string max:5
  blocked_by: array:string max:10

Link Bookmark

name: Bookmark
description: Save and organize links

fields:
  title: string required max:200
  url: uri required
  notes: text max:1000
  tags: array:string max:10
  read: boolean default:false

Generated Output

AT Protocol Lexicon

The markup generates a lexicon JSON file in lexicons/com.statmeet.cards/:

{
  "lexicon": 1,
  "id": "com.statmeet.cards.task",
  "defs": {
    "main": {
      "type": "record",
      "description": "Track tasks with priority",
      "key": "tid",
      "record": {
        "type": "object",
        "required": ["title", "createdAt"],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "status": {
            "type": "string",
            "knownValues": ["todo", "in_progress", "done"],
            "default": "todo"
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          }
        }
      }
    }
  }
}

Python Dataclass

A Python dataclass is also generated for form handling:

@dataclass
class Task:
    title: str
    status: str
    card_name: str
    active: bool
    federated: bool

API Endpoints

List Lexicons

curl https://your-app.com/xrpc/com.statmeet.lexicon.listLexicons

Get Lexicon Definition

curl "https://your-app.com/xrpc/com.statmeet.lexicon.getLexicon?id=com.statmeet.cards.task"

Usage in Cards

  1. Go to Lexicons in the navigation
  2. Click Create Lexicon
  3. Write your markup definition
  4. Click Preview to see the generated JSON
  5. Click Create Lexicon to save

Once created, the lexicon appears in the template selector when creating new cards.

Querying AT Protocol Records

Cards stores federated cards as AT Protocol records in the user’s PDS (Personal Data Server) using the com.statmeet.cards.default collection.

Record URI Format

at://{did}/com.statmeet.cards.default/{rkey}

Example:

at://did:plc:abc123.../com.statmeet.cards.default/3jzfcijpj2z2a

Using curl

List all cards in your PDS:

curl "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=YOUR_DID&collection=com.statmeet.cards.default"

Get a specific record:

curl "https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=YOUR_DID&collection=com.statmeet.cards.default&rkey=RECORD_KEY"

With authentication (for private PDS):

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  "https://your.pds.url/xrpc/com.atproto.repo.listRecords?repo=YOUR_DID&collection=com.statmeet.cards.default"

Using the goat CLI

goat is the official AT Protocol CLI tool.

# Install
go install github.com/bluesky-social/indigo/cmd/goat@latest

# List records
goat repo list-records YOUR_DID com.statmeet.cards.default

# Get specific record
goat repo get-record YOUR_DID com.statmeet.cards.default RKEY

Using Python (atproto SDK)

pip install atproto
from atproto import Client

client = Client()
client.login('your.handle', 'your-app-password')

# List all cards
records = client.com.atproto.repo.list_records({
    'repo': client.me.did,
    'collection': 'com.statmeet.cards.default'
})

for record in records.records:
    print(f"URI: {record.uri}")
    print(f"Data: {record.value}")

Web UI Tools

PDSls

https://pdsls.dev

Enter your DID or handle to browse your entire repo, including custom collections.

atproto-browser

https://atproto-browser.vercel.app

Visual browser for AT Protocol data.

Bluesky PDS Admin

If you run your own PDS, check its admin interface.

Record Schema

Cards are stored with this structure (defined in lexicons/com.statmeet.cards/default.json):

{
  "$type": "com.statmeet.cards.default",
  "card_name": "My Card Title",
  "state": "active",
  "createdAt": "2024-01-25T12:00:00.000Z",
  "details": "Optional detailed description...",
  "tags": ["tag1", "tag2"]
}

Fields

Field Type Required Description
card_name string Yes Title of the card (max 300 chars)
state string Yes One of: queued, active, review, blocked, done, cancel
createdAt datetime Yes ISO 8601 timestamp
details string No Card description (max 10000 chars)
tags string[] No Up to 10 tags (max 50 chars each)

Troubleshooting

Record not found? - Check that the card was successfully federated (look for atproto_uri in the card details) - Verify the DID and rkey are correct - Records are only created when you toggle “Federate” on a card

Authentication errors? - Public endpoints don’t require auth for reading public repos - For your own PDS, you may need a valid access token - App passwords can be created at: https://bsky.app/settings/app-passwords

Workflow Markup Language

A simple markup language for defining custom workflows with states, transitions, and permission rules. Create workflows to control how cards move through your process and who can perform each action.

Quick Start

name: Simple Task
description: Basic task workflow

states:
  todo: To Do
    initial: true

  doing: In Progress

  done: Done
    terminal: true

transitions:
  todo -> doing: Start
    allowed: any

  doing -> done: Complete
    allowed: any

  doing -> todo: Pause
    allowed: any

Syntax

Header Section

Field Required Description
name: Yes Name for the workflow
description: No Description of the workflow’s purpose

States Section

Start with states: then define each state with optional attributes:

states:
  state_key: Display Label
    initial: true
    terminal: true
    assignee: any

State Attributes

Attribute Values Description
initial: true Starting state for new cards (only one per workflow)
terminal: true Final state - workflow is complete
assignee: any, assigned, owner, ai Who can be assigned when card is in this state
ai_prompt: string Task instruction for the AI agent (required when assignee: ai)

ai_prompt supports single-line or multi-line block scalar (YAML | style):

# Single-line
  ai_prompt: Summarize this card and suggest next steps.

# Multi-line (use | then indent continuation lines deeper)
  ai_prompt: |
    You are reviewing this card.
    Summarize the key points.
    Suggest next steps for the assignee.

Transitions Section

Start with transitions: then define allowed state changes:

transitions:
  from_state -> to_state: Transition Label
    allowed: any

Transition Syntax

Pattern Description
state_a -> state_b: Label Transition from state_a to state_b
* -> state: Label Transition from ANY state (wildcard)

Permission Rules

Value Who Can Perform
any Anyone with access to the card (including AI)
assigned Only the assigned user (or owner)
assignee Strictly the assigned user — the owner does NOT qualify. Use when the transition expresses the assignee’s consent (e.g. accepting an invitation)
owner Only the card owner
ai Only the AI agent (humans cannot perform this transition)

Examples

Bug Tracking

name: Bug Tracking
description: Standard workflow for tracking bug fixes

states:
  new: New
    initial: true
    assignee: any

  in_progress: In Progress
    assignee: assigned

  review: Code Review
    assignee: any

  done: Done
    terminal: true

transitions:
  new -> in_progress: Start Work
    allowed: assigned

  in_progress -> review: Submit for Review
    allowed: assigned

  review -> in_progress: Request Changes
    allowed: any

  review -> done: Approve
    allowed: owner

  * -> done: Force Close
    allowed: owner

Content Approval

name: Content Approval
description: Review and publish content

states:
  draft: Draft
    initial: true

  submitted: Submitted for Review

  approved: Approved

  published: Published
    terminal: true

  rejected: Rejected
    terminal: true

transitions:
  draft -> submitted: Submit
    allowed: any

  submitted -> approved: Approve
    allowed: owner

  submitted -> draft: Return for Edits
    allowed: owner

  approved -> published: Publish
    allowed: owner

  submitted -> rejected: Reject
    allowed: owner

Kanban Board

name: Kanban
description: Simple kanban workflow

states:
  backlog: Backlog
    initial: true

  todo: To Do

  doing: In Progress

  done: Done
    terminal: true

transitions:
  backlog -> todo: Prioritize
    allowed: any

  todo -> doing: Start
    allowed: any

  doing -> todo: Pause
    allowed: any

  doing -> done: Complete
    allowed: any

  todo -> backlog: Deprioritize
    allowed: any

AI Agent States

Set assignee: ai on a state to have the AI automatically process cards when they arrive in that state. The AI can update fields, perform a transition, and post a comment explaining its reasoning.

states:
  triage: Triage
    assignee: ai
    ai_prompt: Assess the severity of this bug report. Set priority field to critical, high, or low. Route critical bugs to the critical state and everything else to normal.

Transitions the AI can perform must have allowed: ai. Human users cannot perform allowed: ai transitions — they are AI-exclusive.

transitions:
  triage -> critical: Critical Bug
    allowed: ai

  triage -> normal: Normal Bug
    allowed: ai

If the destination state is also assignee: ai, the AI chains automatically without human intervention.

Requirements: The card owner must have an Anthropic API key configured in Settings. If no key is found, the AI posts a comment on the card explaining why it could not run.

AI Workflow Example

name: Bug Triage
description: AI-assisted bug triage and routing

states:
  new: New
    initial: true
    assignee: any

  triage: Triage
    assignee: ai
    ai_prompt: Assess severity based on the description. Set the priority field to critical, high, or low. Route critical bugs to the critical state, all others to normal.

  critical: Critical
    assignee: assigned

  normal: Normal
    assignee: assigned

  done: Done
    terminal: true

transitions:
  new -> triage: Submit
    allowed: any

  triage -> critical: Critical
    allowed: ai

  triage -> normal: Normal
    allowed: ai

  critical -> done: Resolve
    allowed: assigned

  normal -> done: Resolve
    allowed: any

  * -> done: Force Close
    allowed: owner

User Roles

When a card has a workflow, the system determines the user’s role:

Role Description
owner The user who created the card
assigned The user assigned to the card
other Any other user with access

Role hierarchy: owner can do everything assigned can do, and assigned can do everything any allows.

Cross-User Assignment

Workflows support assigning cards to other Bluesky users:

  1. On a card detail page, enter a Bluesky handle (e.g., @user.bsky.social)
  2. The system resolves the handle to a DID
  3. The assigned user can view the card and perform transitions allowed for assigned role
  4. Assigned cards appear in the user’s “Assigned to me” view

Workflow Diagram

Each workflow displays a visual diagram showing: - Green boxes: Initial state - Red boxes: Terminal states - Blue boxes: Regular states - Arrows: Allowed transitions with labels

Usage in Cards

Creating a Workflow

  1. Go to Workflows in the navigation
  2. Click + Create Workflow
  3. Write your workflow markup
  4. Click Create Workflow

Attaching to a Card

  1. Open a card’s detail page
  2. Use the “Add workflow” dropdown to select a workflow
  3. The card’s state is set to the workflow’s initial state
  4. State selector now only shows allowed transitions

Viewing Assigned Cards

Click Assigned to me on the home page to see cards assigned to you by other users.

API Integration

Workflows are stored with both the original markup and parsed JSON for quick access:

# Get workflow for a card
from modules.workflows import get_workflow_for_card

workflow = get_workflow_for_card(db, card)
if workflow:
    allowed = workflow.get_allowed_transitions(card.state, user_role)
    can_complete = workflow.can_transition(card.state, "done", user_role)

Validation

The system validates workflows on creation:

  • Must have a name
  • Must have at least one state
  • Warns if no initial state is defined (first state is used)
  • Warns if multiple initial states are defined
  • Warns about unreachable states
  • Validates transition references to existing states