CoderFlow Server
CoderFlow is an enterprise platform that runs autonomous engineering agents inside your infrastructure. Instead of merely suggesting code, agents compile, test, validate, and fix legacy systems end-to-end — delivering verified, ready-to-commit results and 5–10x productivity gains.
CoderFlow:
- Submits coding tasks to AI agents (Claude, Codex, Gemini) running in isolated Docker containers
- Lets agents execute code, compile, test, and validate changes automatically
- Supports both headless execution (submit once, review results later) and interactive sessions (work within containers for guided work)
- Manages multi-repository workspaces with build pipelines and test suites
- Allows developers to review, iterate, and approve changes before committing
Reusable Portal data tools
IT can package JavaScript data functions in persistent skills for saved reports and dashboard tiles. See the installation guide and runnable mock package for explicit environment enablement, pinned revisions, and per-user secrets.
Requests in a testing App Server
Task-container App Servers seed their isolated Request store before it opens, alongside
existing task-runtime snapshot behavior. When no local requests/state.json exists,
startup copies up to 10 Requests with the most recent IT activity from the current
task's environment. The host task metadata identifies that environment; missing or
invalid metadata skips seeding rather than copying other environments.
The snapshot includes complete events, reviewed context, receipts and read markers.
IDs, display numbers and the host's next-number counter are retained. Access still
uses current user/environment permissions. Source Ask backlink recovery jobs are
excluded, and no Portal workspace is copied. Changes made while testing stay local.
TASK_RUNTIME_REQUEST_SEED_LIMIT controls the number of Requests (default 10, maximum
100; 0 disables seeding). Input is limited to a 64 MiB host aggregate and the copied
aggregate to 8 MiB. A Request that would exceed the copy budget is skipped whole;
history is never truncated. Invalid/newer-schema or oversized host files are not
copied and leave startup able to use its isolated local store.
An existing local state file always wins, including an empty or corrupt file. Restarts
never replace local edits or automatically refresh from production. The state file
itself is the copy-once fence; .host-request-snapshot.json records snapshot metadata.
A new task runtime can take a fresh snapshot. There is no automatic merge or refresh
operation. Normal host-server startup does not run this seeder.
Quick Start
Prerequisites
Install
npm install -g @profoundlogic/coderflow-server
Initialize
coder-server init mycompany-coder-setup
coder-server config set coder_setup_path mycompany-coder-setup
coder-server license set <your-license-key>
coder-server create-user --username=admin --email=admin@example.com --name="Admin User" --admin
coder-server start
Documentation
For complete installation instructions, configuration options, environment setup, and administration guides, see the full documentation:
CoderFlow Documentation — Installation Guide
For production browser traffic, CoderFlow can terminate TLS and serve HTTP/2
itself without another service. See the
built-in HTTP/2 deployment guide. It uses the
existing certificate settings and retains HTTP/1.1 on the same port for
WebSockets and compatibility clients.
The default auto mode selects built-in HTTP/2 when both a TLS certificate
and key are configured, and HTTP/1.1 otherwise. http_protocol http1 keeps
HTTP/1.1 even with TLS and is the rollback control. Explicit http2 requires
TLS. This applies only when CoderFlow terminates TLS itself; HTTPS terminated
by an upstream Nginx or load balancer does not use this listener.
User activity reporting
Server Administration → Usage includes a User Activity History report and
CSV export for the selected period and environment. It lists all current
accounts (including zero-activity accounts) and former users with activity in
the period. Active days use UTC. The last-activity column is limited to the
selected period. The 7-day and 30-day filters provide weekly and monthly
adoption views.
The report counts task creation and follow-up submissions using the recorded
actor and event time, including follow-ups on older tasks. Known automation,
review-loop events, and automatically created child/judge tasks are counted
separately. Missing attribution and unknown sources appear in the unattributed
count. Attributed API submissions are included: this is not proof that a human
typed each request. Terminal interactions, logins, approvals, and time spent
working are not measured. Counts indicate adoption, not productivity.
Activity is retained in usage-activity/ under the server data directory
(SERVER_DATA_PATH, default ~/.coder/data). Task-container App Servers use
their isolated runtime data directory instead. Include this directory in
server backups. The ledger stores only task IDs, timestamps, user identity,
environment, event type, and source; it does not store prompts, code, or task
titles. Atomic per-task records retain previously captured events through
task deletion and conversation rewinds. Repeated metadata saves do not add
duplicate events. Deletion is refused if the activity record cannot be saved,
so a retry can preserve it before removing the task.
Surviving loaded tasks are backfilled in the background after the host starts
listening and when the report is requested, with historical coverage explicitly
marked partial. Already-deleted or unloaded old tasks cannot be reconstructed
by this backfill. Existing operational task statistics and drilldowns still
describe loaded tasks; the separate activity report survives unloading and
deletion. Activity has no automatic expiry and is retained until an operator
removes the ledger. Do not edit it while the server is running; it has one
writer process per server data directory.
Ledger failures are logged without failing server startup or an already saved
task metadata update. The Usage page shows an activity-history error while
continuing to display operational statistics. Refreshing retries reconciliation
from surviving loaded tasks. Task deletion still requires a successful activity
save. Displayed timestamps use the viewer's local time zone with a zone label;
active-day counts and exported timestamps remain UTC.
Inactive task containers
The normal host cleanup stops inactive task containers after two hours by
default. Set container_cleanup_hours in setup.json (fractional hours are
supported), or use the legacy CONTAINER_CLEANUP_HOURS environment override.
For CPU- or memory-constrained hosts, 0.5–1 hour is recommended; other
installations keep the two-hour default unless explicitly configured. Pinned
task containers retain a 48-hour inactivity grace before they are stopped.
idle_container_cleanup_hours (or IDLE_CONTAINER_CLEANUP_HOURS) sets a
shorter grace for containers whose turn has finished and that nobody is
attached to — no task-terminal WebSocket and no running App Server. It is unset
by default, so behaviour does not change until an operator opts in, and a value
longer than container_cleanup_hours is ignored rather than applied. Pinned
containers keep their 48-hour grace even when this shorter threshold is set.
Stopping such a container costs a restart on the next follow-up; leaving it
running costs a share of the host for the whole linger window. 0.25 is a
reasonable starting point on a busy host. A running, starting, or degraded App
Server is active use and prevents inactivity stopping under both the shortened
and standard thresholds. An attached browser task terminal has the same hard
exclusion until its WebSocket closes. After a CoderFlow server restart, the
initial cleanup pass first probes published ports from the shared inventory and
reconstructs active App Server state, so a still-running preview is not stopped
before its status page is opened. Cleanup re-probes those ports before every
hard exclusion, and container stop/destroy events invalidate the matching state,
so an old running record cannot retain an inactive container indefinitely.
Stopped task containers are removed after seven days by default. Set
stopped_container_retention_days in setup.json, or use the
STOPPED_CONTAINER_RETENTION_DAYS environment override. Finalized containers
(approved and pushed, or losing task-group variants) are removed on the next
cleanup cycle. Containers are retained while their task is still in an active
or otherwise nonrecoverable state; the retention clock comes from the recorded
stop, finish, or agent/container activity time, never task creation or a
read-only task-page view. Pinning extends the running-container inactivity
grace to 48 hours, but it does not prevent either automatic removal policy.
On rollout, a stopped pinned container
without a recorded stop time receives a one-time seven-day grace before any
automatic removal, allowing its owner to mark it protected. Individual
task containers can be protected from automatic removal from the task's More
actions menu. Protection also applies to finalized containers, but it does
not prevent inactivity stopping, explicit removal, or task deletion, and it is
not inherited by forks. A removed task can still be continued as a fork in a
new container from its saved history, although unsaved container-only state is
not recoverable.
Cleanup removes at most 50 eligible containers and admits new Docker operations
for at most 30 seconds per cycle, oldest first; already-admitted metadata and
MCP cleanup finish for consistency, and deferred Docker work continues on the
next ten-minute cycle. Container state, cleanup, health counts, and credential
refreshes share one event-backed CoderFlow inventory. Its safety reconciliation starts at one
minute after a correction and backs off to five, then ten minutes after clean
scans. A shared running-only compatibility scan, refreshed at most every five
minutes, keeps unlabeled legacy coder-* containers current in credential and
entitlement propagation without adding them to every inventory reconciliation.
The Health metrics docker.total, docker.running, and docker.stopped
therefore count CoderFlow-labeled containers in that shared inventory, not every
container on the Docker host. Administrators may see lower totals after this
change when the host also runs unrelated workloads.
Docker storage maintenance probes
capacity first and only runs the more expensive docker df accounting under
configured disk pressure; an admin can also request it explicitly with the
Health page's Refresh action. If pressure-triggered storage accounting fails
or times out, that cycle skips pruning instead of using stale reclaimable-byte
figures while the accounting request may still be using the Docker daemon.
The default auto mode serves HTTP/2 with a TLS certificate and key, and
HTTP/1.1 otherwise. HTTP/2 prevents long-lived SSE streams from filling the
browser's six-connection HTTP/1.1 pool and stalling page loads. Use http1 to
roll back, or use separate TLS termination with the product-specific
Nginx HTTP/2 reference.
Microsoft Teams agent integration
CoderFlow hosts its Teams integration with the Microsoft 365 Agents SDK. The
existing Azure Bot resource and Microsoft Entra app registration remain valid;
configure their messaging endpoint as the public HTTPS URL shown under
Settings > Microsoft Teams, ending in /api/teams/messages.
The saved Microsoft App ID, client secret, app type, and—for SingleTenant
registrations—the Entra tenant ID are used for both inbound JWT validation and
outbound/proactive activity delivery. MultiTenant registrations leave the
Agents SDK tenant unset so each activity's tenant can be resolved normally; any
optional saved tenant ID remains available only as a Microsoft Graph fallback.
The endpoint does not use a CoderFlow browser session, but every inbound activity
must carry a valid Bot Service bearer token.
If INTEGRATIONS_INGRESS_MODE=listener is used, expose the integrations listener
instead of the main application while keeping the same /api/teams/messages
path. After changing credentials, callback routing, or the Teams manifest,
validate personal chats, group chats, channel mentions, threaded follow-ups,
account linking, and proactive completion replies in a development tenant.
SSE stream limits and health metrics
The server limits long-lived task SSE responses per authenticated user, across
all their tabs, sessions and API keys. Configure these positive integer settings
in coder-setup's setup.json, then restart the server:
{
"max_task_streams_per_user": 8,
"max_update_streams_per_user": 4,
"task_stream_warning_threshold": 6
}
These are the defaults; missing or invalid values (including zero) use the
default. Each new response beyond its endpoint's cap evicts that user's oldest
response of the same kind, across all task IDs. The server sends a terminal
stream-evicted event before closing it and immediately clears its polling or
keepalive timer. The Web UI logs eviction to the console and stops automatic
reconnection for that subscription. Close surplus tabs before reopening an
evicted page. An evicted shared updates connection remains stopped for the
worker lifetime, so close all CoderFlow tabs using that worker before reopening.
Warnings are logged once when a user's task stream count rises above the
warning threshold, and re-arm when it falls back to or below the threshold.
Replacing streams while continuously above the threshold does not repeat the
warning. Set the threshold below the task cap to receive warnings before eviction.
Admin GET /health/metrics includes sseStreams: live task/update totals,
effective limits, and users with user ID, username, counts, oldest stream age
in seconds, and a tasks entry (task ID and age) for every task response.
Duplicate task IDs represent separate open responses. The admin Health page
shows the same data in Open SSE Streams. Counts cover this server process;
they do not include other SSE endpoint types or aggregate multiple servers.
Compressing the task activity stream
The first load of a task page downloads the task's whole activity history
through GET /tasks/:id/stream, often tens of MB. Like other responses, the
stream is compressed for browsers that accept it. Every other streaming
endpoint stays uncompressed.
Browsers that accept Brotli get Brotli at quality 4 with a 1 MB window;
others get gzip at level 6. On real task histories Brotli made the stream
2.9–5.4× smaller and gzip 1.4–2.9×, and Brotli took less server CPU: gzip's
32 KB window misses tool output that is repeated further apart. Each open
compressed stream holds its compressor, about 3.4 MB for Brotli at this
window. The route flushes after each replay batch of 50 records, after the
initial replay, at the end of each one-second poll tick that wrote something,
and after events pushed by other routes, so live events are not held back.
With or without compression, the stream is written at the client's pace: when
the connection backs up, the replay, and a poll that finds a large append,
wait for it to drain between batches rather than buffering the rest in server
memory.
Responses carry Cache-Control: no-cache, no-transform and
X-Accel-Buffering: no. A reverse proxy in front of the server should forward
the browser's Accept-Encoding, and must not buffer the response or
decompress and re-compress it; new events should reach the page within about
a second while a task is running. To serve the stream uncompressed through a
proxy that cannot do that, have the proxy stop forwarding Accept-Encoding
for /tasks/*/stream, or send X-No-Compression: 1 with those requests.
Container callback URLs for local servers
Tasks created through MCP and other background triggers resolve their callback
URL in this order: CODERFLOW_INTERNAL_URL, CODERFLOW_SERVER_URL, configured
site_url, then the callback URL learned from an admin request. Task creation
still requires a configured or learned URL; it does not guess a port when none
is available.
For automatically derived URLs, loopback hosts (localhost, IPv4 127.0.0.0/8,
and IPv6 ::1, including IPv4-mapped loopback) become host.docker.internal,
just as for UI launches. Protocol, non-default port, and base path are retained;
query strings, fragments, and trailing slashes are removed. Public hostnames and
non-loopback IPs are unchanged. This works from source and packaged builds,
independently of NODE_ENV, without a local environment-variable workaround.
Explicit environment overrides keep their hostnames, including loopback, for
custom network topologies. No URL is taken from a parent task. Normalization is
limited to container callbacks; server-side URLs and stored configuration are
not rewritten. Task containers using host.docker.internal receive Docker's
host-gateway mapping when no custom mapping already exists (Docker Engine
20.10+). The host server must listen on an interface reachable from Docker;
remote Docker daemons and loopback-only listeners need appropriate networking.
Personal work reporting
Open the task dashboard’s user menu (your avatar), then My Work (my-work.html) to review your recorded
activity for an inclusive date range (up to 366 days). The page automatically uses
the viewer’s browser timezone (UTC if unavailable), without asking for a timezone.
The environment and task-field dropdowns use the shared CustomSelect component. The
report uses the existing durable usage-activity ledger: task creation and
follow-up submissions are selected by the submitting user and event date,
including activity on older, unpinned, or snoozed tasks. It does not interpret
task lifetime or submission counts as human hours.
The authenticated GET /my-work endpoint accepts from, to (YYYY-MM-DD),
timeZone, optional environment, JSON-encoded fields (exact current custom
field values by key), and offset/limit (default 20, maximum 50). Rows are
ordered by descending local date and then task ID/environment. nextOffset
is null on the last page. format=csv exports all matching rows regardless of
pagination, with date/timezone/coverage metadata and spreadsheet-safe escaping.
Each request reads current records; concurrent activity or classification edits
can change the result between pages, so refresh after ongoing work settles for
a consistent review. The CSV is generated from a single report read.
Task details require the same permissions as opening the task. The report is
always personal, including for administrators; it has no user-ID override.
Current custom fields support product, team, category or other customer-defined
classification without a separate schema. Their values and task titles/status
are current metadata, not historical snapshots. Missing task details produce
unlinked placeholders, because the ledger intentionally retains no titles,
code or prompts. Filters on custom fields exclude these unknown values.
The Task activity section groups rows by local date and shows a compact task
and follow-up count. Each row keeps its activity summary visible, with environment,
current status, custom fields and request excerpts under Details. Report
limitations remain under About this report. The activity CSV action is in the
section header; pagination appears only when there is another page to visit.
Rows provide a factual submission summary and up to three 600-character
excerpts of still-retained, matching follow-ups. They describe requests, not
verified completion outcomes. Rewound or deleted text is not reconstructed.
Previously deleted/unloaded tasks, unattributed events, known automation,
recording outages, terminal work and reviews without a submission are not a
complete record of a person's work. Coverage limitations appear on the page
and in CSV exports; the activity report itself persists no additional personal history.
Ask CoderFlow and MCP expose the read-only get_my_work tool with the same
permissions, date filters and pagination. Ask CoderFlow receives the applied
page filters and can summarize results by product or other fields. Its existing
provider/documentation configuration is still required. Model-generated prose
is an interpretation of the returned evidence; embedded task text is data,
never instructions. Time is logged separately in the same page;
this activity tool does not return or modify time entries.
Focused validation:
- Server:
tests/my-work.test.js, tests/my-work-route.test.js,
tests/assistant-tools.test.js, and the get_my_work case in
tests/mcp-server.test.js, using the isolated Node test setup in
tests/README.md.
- Web UI:
tests/my-work.test.js, tests/audit-fields.test.js, and
tests/ask-coderflow-fields.test.js.
- Authenticated UI:
npm run test:ui -- tests/ui/my-work.spec.js --workers=1.
The normal App Server and writable test-user setup are required.
- Isolated browser component (fictional history; does not test authentication):
npm run test:ui -- --config=tests/ui/my-work-component.config.js --workers=1.
Artifacts default to playwright-test-results/my-work/ in the server package.
Set MY_WORK_OUTPUT_DIR to override the artifact root, or
MY_WORK_SCREENSHOT_DIR to override only the screenshot directory.
After building the Web UI, set MY_WORK_ASSET_DIR to its absolute
dist/public path to run these same browser checks against production assets.
Time tracking
My Work includes a Time tracking card. Expand Log time, select a
work date, enter decimal hours (for example, 0.5 or 1.25), and describe the
work. Hours are rounded to the nearest minute. The form supports editing,
removal and a CSV export. Task & notes (optional) links the entry to a task
and adds notes. The task picker lists tasks you created or followed up first,
then tasks shared by others, then automated runs, each most recent first. Each
option shows a readable title (derived from the instructions, deploy profile or
test name when a task was never named), a one-line summary, status, environment,
agent, owner or automation, last activity and task ID. Search matches those
details. Editing existing entries preserves their task links, notes and saved
classifications; their work descriptions can be updated.
Daily and Monday-based weekly totals use the applied dates and filters. Partial
weeks include only dates in the selected range. Each entry retains its work date
and the browser timezone at creation. Neither elapsed task duration nor activity
counts create or prefill reported hours. The daily total across all entries is
limited to 24 hours. These are user-confirmed entries, not manager approvals.
Authenticated endpoints under /my-work:
GET /time-tasks?search=... lists up to 50 permitted tasks in picker order, with
display title and summary, group (mine, shared or automated), kind,
status, agent, createdBy, automation, activityAt and classifications.
GET /time-entries accepts the report date/timezone/environment/field filters;
format=csv exports every matching active entry, independent of activity pagination.
POST /time-entries takes date, timeZone, whole minutes, taskId (empty for
non-task work), label, notes, and fields for non-task classifications.
PUT /time-entries/:id takes the same fields plus the current version.
DELETE /time-entries/:id takes the current version in a JSON body.
Entries are scoped to the authenticated user, with task-detail permission checks
when linking a task. Version conflicts return 409. Entries are atomically stored
per user in work-time under the runtime data directory, separately from task
activity. Edits append revisions; removal retains a tombstone and revisions but
excludes the entry from reports and exports. Active-entry correction history is
visible in the page. Task IDs and classification snapshots survive task deletion;
task titles and prompts are not persisted in this store. Include this directory
in normal server data backups. The store assumes one owning server process.
Validation: tests/work-time.test.js covers persistence, isolation, permissions,
revisions, deletion, conflict handling, totals, CSV, invalid input and HTTP routes.
The existing isolated My Work browser component spec covers creating, editing,
exporting and removing entries as well as the rich environment selector. Timers,
manager approval, Jira synchronization and external database adapters are deferred.