Skip to content

Releases: romirom11/ordi

ordi v1.33.1

Choose a tag to compare

@github-actions github-actions released this 18 Sep 20:26
e4964f9

ordi v1.33.1

One fix: the agent worker stays online. Every heartbeat after a worker's
first one failed, so the worker went offline in Settings → Agents 35 seconds
after it started, and a standalone worker logged an error every 15 seconds.

The agent worker heartbeat

A worker reports itself every 15 seconds: the first heartbeat inserts its row
in agent_workers, every later one updates it. The row's version column
held the ordi version the worker runs, a string like 1.33.0. The
optimistic-locking trigger from triggers.sql attaches to every column named
version and bumps it by one on UPDATE, and '1.33.0' + 1 is not a thing
Postgres does:

operator does not exist: text + integer

So the insert went through and the update never did. In the api container
that was a warning in the log every 15 seconds and a worker that showed
offline; with the worker split out (v1.33.0) it was a 500 from
/api/v1/agent-worker/heartbeat and agent worker heartbeat failed in the
worker's log on every beat. Runs still executed, because claiming does not
depend on the heartbeat, but the status page could not tell a live worker
from a dead one.

The column is now app_version. Migration 0039_agent_workers_app_version
renames it and drops the trigger a previous start attached to the table;
triggers.sql attaches the bump trigger only to integer version columns
from now on, so the next table with a text one does not repeat this. The
API's response shape is unchanged (workers[].version). A test heartbeats
twice over the HTTP backend and checks the worker is online.

Upgrading

Server-side only: redeploy the image. Migration
0039_agent_workers_app_version runs on start. The desktop app is unchanged
apart from its version number.

ordi v1.32.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 21:31
6cc91d3

ordi v1.32.0

One writer attaches branches, commits and pull requests to tasks, and the git
webhook finally says why a delivery linked nothing; the release image is
published to GHCR, so a server pulls the app instead of building it; and the
nginx web image, deprecated since v1.6.0, is removed.

Git links you can debug

  • One writer. Branches, commits and pull requests attach to tasks through
    the forge's webhook – an agent's push exactly as a person's. The agent
    worker used to also copy its pull request into a plain task link, which no
    page renders; that copy is gone. The webhook's link handling lives in
    integrations/git-links.ts; (task, type, ref) is unique on git_links,
    so two deliveries naming the same ref at the same moment leave one row.
  • One event per transition. GitHub's Redeliver sends a delivery again
    under a new id, which the idempotency check lets through; the event it
    carries (opened, merged, closed) now fires only when the row's state
    actually moves, so a redelivered "opened" does not open the pull request
    twice for automations, outgoing webhooks and the stream.
  • The webhook says why. It always answers 200, because a forge retries
    on anything else – so the reason a delivery changed nothing goes to the
    log instead: the signature matched no connection, no connection for the
    installation (reinstall or resync), the repository is not bound to a
    project, no task ref in the branch, commits or pull request, a ref that
    does not resolve. A processed delivery logs what it linked, and an error
    is logged rather than swallowed.
  • The URL GitHub must call is on the settings page. The manifest
    registers the webhook URL from the API_URL of the instance the app was
    created from; an app created from a staging host, or before the domain
    moved, keeps that URL, and every delivery answers 404 – which ordi never
    saw. Settings → Integrations → GitHub now shows the URL this instance
    expects, with a copy button, to paste into the app's settings on GitHub
    and redeliver.

The image is published

  • A server stops building the app. Until now CI built the image and threw
    it away: every install ran pnpm install and the web build on the target
    machine, and no hosting panel had anything to pull. Every release now
    pushes ghcr.io/romirom11/ordi – API, workers and the built web app in one
    container, linux/amd64 – tagged 1.32.0, 1.32, 1, latest and
    sha-<commit>. Pin to the exact version and roll back to it; a pre-release
    gets only its own tag, and a manual run on a branch pushes sha-<commit>
    alone, never latest.
  • The tag cannot lie. /healthz reports package.json's version, so a
    tag that disagrees with it fails the build instead of publishing a wrong
    latest.
  • A green run means the pull works. The deliverable is not a passing
    build: after pushing, the job logs out of the registry, pulls the tag the
    way a stranger would, checks the digest is the one it just pushed, boots it
    against Postgres and requires /healthz to report this version and the web
    app to be served from the same port. If the package is private the
    anonymous pull fails with the one-time fix in the log.
  • The dispatch is deliberate. release.yml dispatches the image build for
    the tag next to the desktop builds. It has to: the tag is pushed with
    GITHUB_TOKEN, which fires no push: tags trigger, and the release is
    published by desktop.yml with the same token, so on: release would never
    fire either. push: tags stays for a tag pushed by hand.

linux/amd64 only, deliberately – building this Dockerfile for arm64 under
QEMU takes tens of minutes. If arm64 is ever needed it should be built on
native arm64 runners and the manifests merged, not added to platforms.

The nginx web image is gone

Breaking for deployments that still pin it. docker/Dockerfile.web and
docker/nginx.conf.template are removed. They were deprecated in v1.6.0, when
the API image started serving the SPA, /assets/*, SSE and OAuth discovery
itself – neither compose file has referenced a web service since, so the
image's only remaining purpose was deployments that pinned it.

If you still run ordi-web: point your domain at the api service,
port 3000, path /, then delete the web service and any nginx config
mount you added for it. No new env vars. docs/deployment.md keeps the
pre-v1.6.0 migration step for anyone coming from further back.

CI drops the web image build and the step that booted it to check discovery
through nginx. That coverage is not lost: the same assertion against forwarded
Host/X-Forwarded-Proto already runs against the API image, which is what
serves discovery now.

Docs

The README's Features section sold about two thirds of ordi – no Slack, no
GitHub App, no intake, no CRM analytics or playbooks, no receivables aging, no
team calendar, field groups or questionnaire, no files, reactions or setup
wizard, all of it shipped and all of it on the landing page. The section now
mirrors the site, with every line checked against the API and the schema.
"Migrations run as a separate deploy step" is corrected here and in
architecture-decisions.md: the api container has run them on start since
v1.6.0.

Upgrading

Migration 0038_git_links_unique_ref makes (task, type, ref) unique on
git_links, deleting duplicate rows first – the oldest of each set stays. The
api container runs it on start.

Pull the image and restart:

docker compose pull && docker compose up -d

latest moves you to this release; ORDI_VERSION=1.32.0 pins it. The desktop
app updates itself from the release.

If you still run the ordi-web image, see The nginx web image is gone
above before redeploying – that service no longer has an image to pull.

ordi v1.31.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 11:15
404b4c9

ordi v1.31.0

Notifications stop shouting at the whole project and address the person they
concern, the CRM exports a lead with every field it holds, .md attachments
open as documents instead of raw text, the "My leave" card lists your own
absences again, and the MinIO images come from a registry that still serves
them.

Notifications reach the right person

  • One recipient per notification. Every notification was derived from a
    business event, and every business event streams to a whole project – so
    "a task was assigned to you" reached the entire team for a task assigned
    to one of them, and the desktop app raised an OS notification for each of
    them. Each row is now streamed to its recipient alone as
    notification.created, after it has been written, and the web and desktop
    clients key off that frame instead of guessing whether a project event was
    meant for them.
  • Who hears about what, corrected along the way: the person who caused an
    event is no longer told about their own click; deactivated accounts and
    agents are dropped; a comment reaches the task's author, its assignees and
    the people who answered on it before, not only the @-mentioned (the
    mentioned keep their own notification and do not get the comment twice);
    an agent's comments stay quiet, because its run events already report the
    same news.
  • The money events had no recipient at all. A paid invoice, a recorded
    payment and an accepted quote notified nobody: the event names the
    document, so the invoice or quote now supplies the author, the client's
    owner and the ref the email was printing empty. A declined quote is
    reported like an accepted one. A merged pull request asks the task for its
    people instead of trusting an assigneeIds: [] the webhook could not have
    filled. A mention on a knowledge page is filed under page.mentioned, so
    it reads as a page mention and opens the page.
  • Every type can be switched off. Comments, page mentions, merged pull
    requests, recorded payments and declined quotes are notifications of their
    own now, each with its own switch in email preferences – without one they
    would be the only types a person cannot turn off, and the two that used to
    ride along with another type would silently stop obeying the switch that
    covered them.
  • The labels and the deep links live in one lib/notifications, shared by
    the bell and the stream instead of a copy each, so a new type can no longer
    read as task_assigned in one of them; the link table gains the cases the
    bell was missing (a page mention opened the task list, a merged pull
    request opened nothing) and is asserted against the table the emails use.
    The bell no longer refetches on every event, and the stream stops
    reconnecting when the language changes.

Leads export, whole

  • Every field, one lead per row. The leads CSV carried eleven of the
    thirty-odd fields a lead stores, so the answer to "give me the leads in a
    spreadsheet" was to open each record by hand. /export/leads.csv now
    builds the whole row: every column of the lead, the related names the table
    only hints at (company, contact, owner, labels, next action, converted
    deal) and the workspace's own lead custom fields, with select values
    rendered as their labels.
  • It exports what is on screen. An Export button next to the lead
    filters, for whoever holds crm.export, sends the active filters along.
    The export is uncapped – one that stopped at the table's 200 rows would
    look complete and not be.
  • Excel reads them correctly. Exports open with a UTF-8 BOM, which is
    what makes Excel read them as UTF-8 instead of mangling every accent; the
    importer skips a leading BOM, so a file saved by a spreadsheet still
    imports.
  • The download goes through fetch rather than a plain <a href>: a
    navigation carries cookies but never the Authorization header, so on the
    desktop build a link answered 401 instead of a file.

Markdown files read as documents

  • A .md attachment opens formatted. Readmes, specs and exports used to
    open as raw monospace text in the file preview. A markdownToDoc converter
    in @ordi/shared parses a deliberate CommonMark/GFM subset – headings,
    fenced code, blockquotes, bullet / ordered / task lists, pipe tables,
    horizontal rules and the inline marks the editor itself offers – into a
    tiptap document, which the preview renders through the same read-only
    renderer as notes and KB pages.
  • Safe by construction: the markdown becomes a JSON tree, so no raw HTML
    ever reaches the page. Links survive only for http, https and mailto,
    and images become links rather than <img>, so opening a preview never
    fetches a third-party URL.

Fixes

  • "My leave" lists only your own requests. GET /leave-requests without
    an employeeId returns everyone's once the caller holds people.read
    right for the team calendar, the People page and Resourcing, and exactly
    wrong for the self-service card, which was calling the bare endpoint: an HR
    user's own profile listed other employees' absences, each with a cancel
    button beside it. A ?scope=mine alongside the existing ?scope=approvals
    makes every read the card does explicitly about the caller. An account with
    no employee record still gets a 403 there, so the card keeps showing its
    "not linked" hint. Balances were never affected – /leave-entitlements is
    scoped by employee.
  • MinIO images pull again. Anonymous pulls of minio/mc and
    minio/minio on Docker Hub now answer 401, so a fresh deploy with the
    minio profile failed at minio/mc:latest with "pull access denied" and
    only a cached image kept existing servers running. Both images come from
    quay.io/minio/… now, pinned to releases that ship /usr/bin/mc, which
    the container healthcheck relies on.

Site

The landing page was last revised at v1.27 and had nothing on agent employees
(v1.29), the product's biggest addition since. A feature block after the MCP
section explains the flow – assign, clone, headless Claude Code with the ordi
MCP server, pull request, review – and the GitHub card notes that agents open
PRs through the same App. The desktop step mentions browser sign-in and the
download link in the web app's settings. "Timesheet approval" is dropped from
the Time card and the README: time entries have no approval state or route,
only timers, rates, a weekly view, reports and unbilled time.

Upgrading

No migration. Redeploy the API and web images (or the single image); the
desktop app updates itself from the release.

If you run the bundled minio profile, pull the images again after updating
the compose file – they now come from quay.io/minio/minio and
quay.io/minio/mc at pinned releases, and the old minio/*:latest tags are
no longer pullable anonymously. A running MinIO keeps its data: only the
image reference changes.

ordi v1.30.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 13:00
7d01605

ordi v1.30.0

The first release built by ordi's own agents: three tasks from the board
(ORD-24, ORD-26, ORD-29) went from assignment to pull request inside the
deployment, were reviewed and merged. What they shipped – leave balances a
person can see and cannot overdraw, comments that can be edited after the
fact, and money records that start in the workspace currency – plus the
pull-request hygiene the agents themselves learned along the way.

Leave balances people can see

  • Remaining days per leave type: GET /leave-entitlements returns
    allocated, carried, used, pending and remaining per type for a person
    and period. Until HR has accrued the period the type's annual quota is
    the allocation, so a profile never reads "0 of 0" just because nobody
    ran the accrual yet; a type with no quota, or one that does not draw down
    a balance, is unlimited and says so. Your own card without people.read,
    anyone's with it.
  • Nobody books more than they have: a request longer than what is left
    is refused at submit, with pending requests already held back – two
    requests that each fit cannot together exceed the quota – and again at
    approval, in case the quota was lowered or another request approved in
    between. A period accrued while the quota was 0 keeps its cap once the
    quota is raised: it reads "0 days left until HR re-accrues", not
    "unlimited". The submit-time checks and the insert run under a lock on
    the employee, so two simultaneous requests are checked one after the
    other.
  • The leave card shows what is left: remaining-of-allocated per type
    with pending days called out, on your own card and – for anyone with
    people.read – on every other employee's page, where HR is asked for it.
    The request dialog previews the working days a range costs (weekends and
    holidays excluded, with the same shared calc the API charges by) and what
    it leaves, and blocks an over-quota submit before it is sent. Request
    rows show the deducted working days instead of the calendar span.
  • The one rule lives in @ordi/shared (remainingBalance,
    exceedsEntitlement) so the card, the form and the API agree.

Comments can be edited

  • Edit in place: a pencil on any comment you may rewrite – your own as
    a project member, anyone's as a project admin, the same rule the API
    enforces on delete – swaps the body for the same rich composer the thread
    posts with. ⌘↵ saves, Esc discards, and an edited comment carries an
    "(edited)" marker with the time in its tooltip.
  • PATCH /comments/:id hardened before exposure: the body is required
    and must contain something – a missing key, null or an empty document
    each used to validate and blank the comment. A mention introduced by an
    edit notifies once; mentions already in the comment stay quiet. The
    rewrite is written to the activity log.

Money starts in the workspace currency

  • Record pickers offer USD, EUR and UAH: the five-code list that lived
    in four copies is one lib/currency.ts; a record already stored in
    another code (an old PLN expense, a GBP company) keeps it in the picker,
    so editing never rewrites a currency silently. The workspace base
    currency in Settings keeps its wide list.
  • Every form seeds from the workspace default: "Add income", the
    subscription dialog and the new client and new deal dialogs start in the
    workspace currency through one useDefaultCurrency() hook, instead of a
    hardcoded USD – a UAH workspace stops booking in dollars unless someone
    noticed the picker.

Agents

  • Pull request descriptions follow the repository's template: the
    checkout is searched for a template at the paths GitHub uses, the brief
    quotes it, and the description is built from the agent's report
    (change, verification, risks) with a link back to the task – saying
    outright when nothing was verified. The checklist is left unticked.
  • Commits carry the task key in the subject where the repository's
    convention allows it, else as a Refs: trailer, so the webhook links
    them to the task; no Co-Authored-By or model trailers. The title
    collapses to one line, so a trailing space on a card no longer lands in
    git.

Upgrading

No migration. Redeploy the API and web images (or the single image); the
desktop app updates itself from the release.

ordi v1.29.4

Choose a tag to compare

@github-actions github-actions released this 09 Sep 00:17
1b303ec

ordi v1.29.4

One change: branches get English names whatever language the task is
written in.

English branch names in any task language

v1.29.3 transliterated a Ukrainian title into the branch slug
(feature/ord-26-dodaty-dostupnu-kilkist-dniv). Now, before cloning, the
worker asks the cheapest model (haiku, one turn, no tools, through the
same Agent SDK, so a subscription token works) for a 3–6 word English slug
from the task title and description: feature/ord-26-show-remaining-leave-days.
It costs a few seconds and a few hundred tokens per new branch. Without an
answer (the model is down, the answer is not a slug) the transliterated name
is used; "Copy branch name" on the task page keeps the transliteration.

A retry whose previous branch never reached origin (a refused push, or a
feature/ord-26- name left by v1.29.2) is named afresh; a branch that
exists on origin, or a kept checkout with unpushed commits, is continued as
before.

Upgrading

Server-side only, no migration: redeploy the API image.

ordi v1.29.3

Choose a tag to compare

@github-actions github-actions released this 08 Sep 23:48
fbc7034

ordi v1.29.3

One fix from the first agent run on a task titled in Ukrainian.

Branch names for non-Latin titles

The branch slug kept only ASCII letters and digits, so a title written in
Ukrainian (or any Cyrillic, or with accented Latin letters) produced
feature/ord-26-: the key and the number with nothing after the dash. The
slug now transliterates Cyrillic with the Ukrainian official romanization,
strips accents from Latin letters, and when nothing of the title survives
(an emoji or CJK title) the branch is feature/ord-26 without a dangling
dash. The same helper feeds "Copy branch name" on the task page, so people
get readable branches too.

Runs already on a feature/<key>-<n>- branch keep it: follow-ups and Retry
continue on the branch recorded on the run.

A refused push loses nothing and leaks nothing

The first run against a GitHub App installation that had not yet accepted
the write permissions ended with 403 on push, and the platform then
deleted the checkout with 18 minutes of committed work. Now:

  • the checkout is kept when the push fails, and the next run for the task
    (Retry) continues in it instead of cloning afresh, so the commits go out
    once the access is fixed;
  • the error says what is wrong and what to do: for a GitHub App, an owner
    accepts the updated permissions under GitHub → Settings → Applications →
    Installed GitHub Apps; for a token, it needs repo write;
  • git errors no longer include the http.extraheader argument, which was
    the installation token in base64, in the run log or the task comment;
  • long lines in the run log wrap instead of pushing the block sideways.

Upgrading

Server-side only, no migration: redeploy the API image. If your GitHub App
was installed before v1.29.0, accept its pending permission request
(contents: write, pull_requests: write) once; until then every agent
push is refused with 403 and the run says so.

ordi v1.29.2

Choose a tag to compare

@github-actions github-actions released this 08 Sep 23:01
d0a98d2

ordi v1.29.2

Everything the first real agent runs turned up, in one release: the runtime
session now survives between runs, work is never thrown away on any early
stop, and the run log, notifications and Retry behave on the task page.

Sessions survive between runs

Resuming never worked: the runtime's home directory, where Claude Code writes
the session transcript, was created per run and deleted with it, so every
follow-up comment and every Retry answered "No conversation found with
session ID". The work directory is now keyed by task –
tasks/<taskId>/checkout for the clone (still deleted after the run) and
tasks/<taskId>/harness for the runtime's home, which stays. The worker
deletes task directories untouched for 30 days.

When the session really is gone (a rebuilt volume, another worker's disk, the
prune), the run no longer fails: it logs "Session … is not on this worker",
starts a fresh session with the full brief, and tells the agent that the
earlier commits are on the branch and to read git log before continuing.

Nothing is lost on an early stop

  • Rate limits: a run parked for quota now pushes what it has and resumes
    the same session on the same branch when the window resets; before, it
    started from scratch with an empty checkout.
  • Worker lost mid-run: the session id and the branch are written to the
    run as soon as they are known, so a re-queued run continues instead of
    starting over.
  • Any error while finalizing (a database blip, the agent user gone) pushes
    the work before the run is marked failed.
  • Long runs: the push and the pull request use a freshly minted GitHub
    installation token; the one from the clone expires after an hour.
  • A failing rev-list (renamed default branch, shallow history) no longer
    reads as "nothing to push".

What goes into the commit

The worker used to git add -A. It now stages tracked edits and new files
that pass a junk filter: no node_modules/, dist/, build/, coverage/,
.env*, logs or caches, and never more than 500 untracked files at once.

Runs and tasks

  • One active run per task and agent: two agents on one task each get
    their own run (migration 0037_agent_runs_task_agent).
  • Follow-ups and Retry carry the branch, so renaming the task between runs
    no longer opens a second branch and pull request.
  • A comment written while the run was still queued is picked up when the run
    ends instead of being dropped.
  • A task in a done or cancelled status gets no run, and a task closed while
    the agent works is left closed.
  • Comments by other agents never trigger a run.
  • An agent's own run token cannot cancel or retry runs.

The runtime

  • The repository's CLAUDE.md and .claude/settings.json are loaded
    (settingSources: ['project']), so the agent follows the project's own
    conventions and checks.
  • The ordi MCP tools are in the prompt from turn one instead of behind
    tool search.
  • BashOutput, KillShell and Skill are allowed, so background commands
    are usable.
  • The worker heartbeat reports the runtime as available only when the
    platform binary actually resolves.

The task page

  • The run log shows the last 500 events, not the first, so the result
    and the error of a long run are visible; streamed events are appended to
    the log instead of refetching it, and the run rows refresh every 5 s while
    a run is active even without SSE.
  • Retry is offered on the newest run only and is disabled while a run is
    active, with a translated message instead of a raw 422.
  • Agent notifications are translated and link to the task or to Settings →
    Agents.
  • The agent profile form accepts up to 2000 turns and explains what a turn
    is; the budget field says it applies to API-key credentials only; adding
    a credential defaults to "Not in rotation" when a primary exists; a
    version conflict on Save reloads the row.
  • Runs that failed before their first turn no longer show "0 turns · $0.00",
    and a pending cancel is no longer painted as an error.

Upgrading

Server-side only: redeploy the API image. Migration
0037_agent_runs_task_agent replaces the one-active-run-per-task index with
one per task and agent. Runs that failed with "No conversation found" before
the upgrade can be retried from the task page: their session is gone, so the
retry starts fresh on the task's branch. If several containers claim runs,
mount the same agent_work volume on all of them.

ordi v1.29.1

Choose a tag to compare

@github-actions github-actions released this 08 Sep 20:31
949767f

ordi v1.29.1

A fix from the first real agent task: the run reached its step limit before
the agent could report, and the platform threw its work away. Nothing an
agent commits is lost any more, and the default limit is one a real task
fits into.

Unfinished work survives

A run that hit max_turns, ran past max_run_minutes or was cancelled used
to post a bare error comment and delete the checkout, so the next attempt
started from an empty branch. Now the worker pushes whatever the agent
committed to the task branch – loose changes go in as a WIP commit – and
records the branch on the run, without opening a pull request. The comment
on the task says where the work is and which limit stopped it: "Max turns"
in the agent profile for the step limit, "Max budget" for the spend cap.

Retry continues instead of restarting: it resumes the same Claude session
on the same branch, and the brief tells the agent to check git log and
git status first and finish with as few steps as possible.

200 turns by default

A turn is one model response, which may carry several tool calls, so 60
turns was too few for a task that starts by reading a code base. The default
max_turns moves to 200 and profiles still on 60 follow it; the profile
field accepts up to 2000.

Upgrading

Server-side only: redeploy the API image. Migration 0036_agent_max_turns
changes the column default and moves existing profiles from 60 to 200.

ordi v1.29.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 12:38
797461b

ordi v1.29.0

The agents release: an AI agent joins the team like any other member, takes
a task and drives it to a pull request inside your own deployment. Around
it, the people side gets the hygiene a real team needs – deactivating
someone hands their open work over instead of orphaning it, a deactivated
assignee can finally be taken off a task, and a task filed under the wrong
project moves instead of being retyped.

AI agent employees

  • An agent is a member: the "Add member" dialog gains a switch, "This is
    an AI agent" – it creates a users row with actor_type = 'agent', no
    password and no invite email, plus an agent profile: runtime (claude_code
    now; codex is listed as coming soon), free-text instructions, the status
    category a finished run lands in (in_review by default), max_run_minutes,
    max_turns, an optional max_budget_usd, per-agent concurrency, and an
    assign policy that says who may hand it work. Agents can be disabled and
    re-enabled; avatars carry an agent badge everywhere a person is shown.
  • Assigning a task is the trigger: task.assigned to an agent queues a
    run. A worker inside the api container claims it (FOR UPDATE SKIP LOCKED, so two workers never run the same job), clones the project's
    repository into a fresh checkout, launches Claude Code headless through
    the Agent SDK with the ordi MCP server and the agent's connectors, and
    streams every SDK message, tool call and result to the task page live
    (GET /agent-runs/:id/events, SSE). The run ends with a branch pushed, a
    pull request opened with the GitHub App installation token, and the task
    in review. Humans review and merge; the existing pr_merged git rule
    closes the task. A comment on the task continues the same Claude session.
    Runs can be cancelled and retried from the task page.
  • Claude is connected once, at workspace level (Settings → Agents,
    agents.manage): either a subscription token from claude setup-token
    (Pro/Max/Team/Enterprise; lives a year, ordi reminds agents.manage
    holders 14 days before it expires) or an Anthropic API key. The secret is
    AES-GCM encrypted with ENCRYPTION_KEY and never returned to the client;
    the card shows who connected it and when, since every run spends that
    plan. "Verify" makes a minimal request and records the result.
    ANTHROPIC_API_KEY in the environment is accepted as a read-only fallback
    for API keys, for PaaS setups configured entirely by env.
  • A connector library with a per-agent allowlist (Settings → Connectors,
    integrations.manage): MCP connectors are added once, from a curated
    library or by URL, and each agent is granted the ones it may use. The
    agent never talks to a connector directly – every call goes through the
    ordi gateway (POST /api/v1/mcp-connectors/:slug/mcp), so no upstream
    secret reaches the agent process. OAuth connectors are authorised by ordi
    itself (discovery, dynamic registration, PKCE) with the callback at
    APP_URL/api/v1/mcp-connectors/oauth/callback; tokens refresh mid-run and
    a connector whose refresh failed shows as needs_auth.
  • Sandboxed by construction: the child process gets a rebuilt environment
    without DATABASE_URL, ENCRYPTION_KEY, AUTH_SECRET, S3 or SMTP
    settings; it authenticates to ordi with a per-run token that is revoked
    when the run ends; tool use is governed by allow/deny lists
    (docs/architecture-decisions.md §12). The agent never closes a coding
    task itself.
  • RBAC: one new permission domain, agents.manage (connect Claude,
    create agents, limits, assign policy, connector grants, cancelling runs) –
    migration 0035 backfills it into Owner and Admin. Agents run under the
    new Agent preset role (projects.read, projects.write, kb.read) and see
    only the projects they are members of; role ∩ token scope applies to every
    call as for any user.
  • GitHub App: the manifest now asks for contents: write and
    pull_requests: write so the agent can push its branch and open the PR.
    New apps are created with them; an existing installation has to accept the
    new permissions (Settings → Integrations shows the state) – until then
    reads and webhooks work and the agent's push fails.
  • API: /agents (list, create, get, patch, enable, disable, overview),
    /agent-credentials (CRUD + verify), /mcp-connectors (library, custom,
    test, OAuth start/callback/revoke), /agent-runs (list, get, events,
    cancel, retry).

People and tasks

  • Deactivation hands work over (ORD-20): "Deactivate" in Settings →
    Users opens a dialog listing the person's open tasks and asking who takes
    them – a successor from the active members, or "leave unassigned". Open
    (backlog / todo / in progress) tasks lose the leaver and gain the
    successor; closed tasks keep their history. Each moved task gets a
    reassigned activity row and the successor is notified like any new
    assignee. GET /users/:id/open-tasks previews the hand-off;
    POST /users/:id/deactivate takes an optional { reassignTo } and refuses
    the leaver themself or another deactivated user. Callers without a body
    get the unassign behaviour.
  • A deactivated assignee can be taken off a task (ORD-22): the assignee
    picker still offers active people only, but anyone already on the task
    stays in the list, marked "deactivated" and muted, so they can be
    un-ticked. Before this the task was stuck with an assignee nobody could
    remove.
  • Move to project (ORD-23): the task row's context menu gains "Move to
    project" next to "Delete", with a confirm dialog and a toast naming the
    new reference. The move follows the agreed rules: a new number in the
    target (the old link redirects), the status of the same name – else the
    first of the same category, else the default – for the task and each
    sub-task, custom fields keep only the keys the target project or the
    workspace defines, labels (workspace-wide) stay, type / cycle / milestone
    clear. Sub-tasks, comments, relations, attachments, git links and logged
    time travel as before. Moving now needs admin rights on both projects.
  • Quick-create picks the open project (ORD-21): on any /projects/:id
    route the composer starts with that project instead of the last one used;
    a project picked by hand in the same session of the dialog sticks.
  • Expense currency is a list (ORD-24): the "New expense" form's currency
    is the same select the income form uses, starting at the workspace
    default instead of a free-text "USD".

Operations

  • AGENT_WORKER_ENABLED (default 1), AGENT_WORKER_CONCURRENCY (2),
    AGENT_WORK_DIR (/data/agent-work) and the optional ANTHROPIC_API_KEY
    fallback – see docs/deployment.md §3b. Both compose files mount a named
    agent_work volume: one fresh clone per run, deleted when the run ends,
    so size it for concurrency × repository size.
  • docker-compose.prod.yml carries a commented-out agent-worker service
    for running agents in their own container (same image, same
    DATABASE_URL / ENCRYPTION_KEY / APP_URL, no published port) with
    AGENT_WORKER_ENABLED=0 on api. Off by default so docker compose up
    still gives a working system out of the box.
  • Workers heartbeat to agent_workers every 15 s; a run whose worker
    vanished is re-queued. Runbook for stuck runs, needs_auth connectors,
    event-log growth and disk: docs/operations.md §6.
  • The API image now ships git and the Claude Agent SDK; the build fails if
    the runtime binary does not resolve.

Migrations

  • 0035_ai_agents – new tables agent_profiles, agent_credentials,
    agent_connectors, agent_runs, agent_run_events, agent_workers,
    mcp_connectors, mcp_connector_oauth; backfills agents.manage into
    the Owner and Admin roles.

ordi v1.28.0

Choose a tag to compare

@github-actions github-actions released this 19 Aug 19:53
412388c

ordi v1.28.0

The one-person release: an account and its HR card stop being two loose
records with two spellings of the same name, agents finally see the
screenshots pasted into task descriptions, and the task surface picks up
three quality-of-life wins – editable labels, in-row status changes and
Slack-style comment reactions.

One name per person

  • The HR card is the canonical spelling: /users/lookup answers with the
    linked employee card's full name (an active card wins over a terminated
    one), falling back to users.name only when no card is linked – so member
    lists, assignee pickers, @mentions and comments stop showing whatever the
    person typed at signup.
  • Both doors lead into one room: every card write with a linked user
    overwrites users.name with the card's full name, and renaming yourself in
    the profile walks back into the card (first word → first name, the rest →
    last name). Migration 0034 backfills existing linked accounts once.
  • The pairing creates itself: accepting an invite links the card that was
    waiting under that email (the account takes the card's name) or creates a
    linked card from the invite; a card created with an email that already
    belongs to an account links on the spot.
  • A broken pair is loud: GET /people/link-suggestions (people.write)
    lists unlinked cards whose email belongs to an account, and the Employees
    directory shows them as a banner with a one-click Link action.
    Accounts with no card were already covered by the "No profile" filter.
  • The profile keeps account things only: the questionnaire and leave
    self-service moved to the person's own employee page (the field-group
    editor there already honours self grants), and the profile links over
    via a "My HR card" row.

Agents see embedded images

  • docToText renders an image node as an ![name](url) line in place, and
    textToDoc turns such a line back into an image node – a screenshot-only
    bug report is no longer an empty body over MCP, and an agent rewriting a
    description no longer destroys the images in it.
  • The MCP layer resolves the stored root-relative signed path to an absolute
    url the agent can actually fetch (no sign-in needed – PRD §14.5 links) and
    strips the origin again on writes, so stored documents stay
    domain-independent. The HTTP-served MCP resolves against APP_URL via the
    new publicUrl client option, since its API base url only exists on
    localhost.
  • Upsert fingerprints compare the stored (relative) form, so re-running a
    generation over a body with images is not mistaken for a hand edit.

Tasks

  • Labels are editable (settings.manage, the same bar as creating):
    PATCH /labels/:id renames and recolors, and the picker gains an in-place
    editor behind a pencil on each row – name, the eight-colour palette, and
    delete (the FK cascade detaches the label from every task).
  • Status changes from the list: the status icon on a task row is a
    picker – click, choose, the row moves. Same optimistic mutation the board
    drag uses; the right-click menu kept offering it too.
  • Comment reactions, Slack-style: one POST /comments/:id/reactions
    toggles the caller's emoji on and off (stored in the reactions column
    the schema always had). Chips with counts under each comment – yours
    highlighted, the tooltip names who reacted – plus a quick palette behind a
    smile button that shows on hover. MCP get_task returns the map per
    comment.

HR calendar

  • Holiday names wrap to two lines before truncating, and a day with events
    opens a popover listing the whole day in full: holidays, birthdays (full
    names) and absences with their type and approval state. Works on touch,
    where the old hover-only tooltip never fired.

Migrations

  • 0034_sync_user_names – data-only: backfills users.name from linked,
    non-deleted employee cards (active over terminated, newest breaks ties).
    No schema changes.