Overview
PolarisLink is an open protocol for governed multi-agent coordination, telemetry and provenance. One gateway gives software agents, research systems and human approvers a shared wire contract: a REST surface for commands and queries, a Server-Sent Events (SSE) stream for live state, scoped API keys for access control, and an audit trail for every run and every write.
The protocol is domain-agnostic. A deployment implements the core protocol in sections 4 and 5 and may add domain modules on the same wire contract. The reference deployment operated by Forticia Research Institute includes one example domain module, for market data, described in section 6.
This page is the public edition of the specification. It documents the wire contract of protocol version 2.6.1. The canonical, versioned text is published in the PolarisLink repository under the Apache License 2.0.
| Property | Value |
|---|---|
| Protocol version | 2.6.1 |
| Transport | HTTPS REST (JSON) and Server-Sent Events |
| Authentication | Scoped API keys, bearer token or API key header |
| Hosted gateway | https://www.forticia.uk/api/polarislink (approved key required) |
| Development gateway | http://localhost:8787/api/polarislink |
| Reference clients | Python 3 (standard library only) and C++20 (single header) |
| Licence | Apache-2.0 |
C++20 client Python client Console and web
| | |
+------------------+---------------------+
|
PolarisLink gateway (REST + SSE)
+------------------+---------------------+
| | |
Key guard Event bus Watchdog
(scoped keys) (live streaming) (issue triage)
| | |
+------------------+---------------------+
|
Run ledger, task store, issue store, driveConventions
Routing and versioning
PolarisLink exposes two complementary route families.
- Unversioned routes under
/api/polarislink/*are the primary surface for agents and services. A small set of root shorthands such asGET /api/status,GET /api/specandGET /api/changelogis also provided. - Versioned compatibility routes under
/api/v1/*form a contract stability boundary for legacy harnesses and scheduled pipelines. A breaking change to the wire format would be introduced under/api/v2/*, so clients on/api/v1/*keep working.
Versioning has three independent tiers.
- The wire route prefix (
/api/v1) changes only for a breaking transport change. - The protocol semantic version (currently 2.6.1) is advertised in response headers and in the health payload. A major version marks an architectural overhaul, a minor version adds capabilities without breaking existing ones, and a patch version carries fixes with no schema change.
- The client libraries follow the protocol version in lockstep.
Standard headers
Every response carries provenance headers:
X-PolarisLink-Protocol: Forticia PolarisLink
X-PolarisLink-Version: 2.6.1
Content-Type: application/json; charset=utf-8Provenance of data
A gateway must never fabricate a value. When a source cannot supply a field, the field is returned as null and the response carries an explicit quality flag, so a consumer can tell a missing value from a zero:
{
"quotesVerified": false,
"dataQuality": "midpoint_bar_only"
}The same rule applies to every domain module: no synthetic, randomised or heuristic values, and no unverified sources.
Errors
Errors are JSON objects with a human-readable error field. Problem-detail payloads (RFC 7807) are used where a route gap needs guidance.
| Status | Meaning |
|---|---|
200 OK |
Successful query or stream handshake |
201 Created |
Entity created (runs, issues, tasks) |
400 Bad Request |
Missing parameters or malformed payload |
401 Unauthorized |
Missing or invalid credentials |
403 Forbidden |
The key lacks the required scope, or a workspace boundary was crossed |
404 Not Found |
The entity or file does not exist |
409 Conflict |
A lease or claim is held by another participant |
429 Too Many Requests |
The key exceeded its rate ceiling |
500 Internal Server Error |
Gateway or storage fault |
Authentication and scopes
The gateway uses a zero-trust bearer-token guard. Keys are compared in constant time. A key provisioned through the gateway is shown once and stored only as a SHA-256 hash. Credentials are sent in a standard header:
Authorization: Bearer <API_KEY>An equivalent header is also accepted:
X-API-Key: <API_KEY>Scopes and rate ceilings
Every key carries one or more scopes. A scope names a capability family and bounds the endpoints a key can reach. Rate ceilings are per key and per minute; the values below are those of the hosted gateway.
| Scope family | Capability | Typical ceiling |
|---|---|---|
admin:* |
Unrestricted administration | 10,000 requests per minute |
ops:agent:telemetry |
Telemetry, heartbeats and the event stream | 5,000 requests per minute |
ops:swarm:read, ops:swarm:manage |
Coordination plane: read state, manage leases, work and operator controls | 5,000 requests per minute |
ops:issues:file, ops:issues:comment |
File and comment on issues inside one workspace | per key |
ops:autoheal |
Automated triage of failing routes | 2,000 requests per minute |
ops:workspace-keys:provision |
Mint workspace-bound keys | per key |
| Domain scopes | Read or write access to a domain module, for example quant:market_data:read |
300 to 5,000 requests per minute |
Workspace-bound keys
A key can be bound to a single workspace at creation. Delegation is limited to one depth: an administrator creates a platform key, and a platform key creates agent keys inside its own workspace. Agent keys carry only the scopes to file and comment on issues. They cannot create further keys and cannot reach any other surface. A request that names a different workspace from the one the key is bound to receives 403 Workspace boundary violation.
Core protocol
All routes in this section are under /api/polarislink.
Telemetry
GET /telemetry returns gateway health for a caller with ops:agent:telemetry: operational status, protocol version, process uptime, the number of open streams, the number of recorded runs, memory use, and the identity and scopes of the calling principal.
{
"status": "operational",
"gateway": "PolarisLink Agent Gateway v2.6.1",
"protocolVersion": "2.6.1",
"uptimeSeconds": 0,
"activeStreams": 0,
"autoHealEngine": "operational",
"agentContext": {
"principalId": "agent-a",
"role": "researcher",
"scopes": ["ops:agent:telemetry"]
},
"runsRecorded": 0,
"timestamp": "2026-01-01T00:00:00.000Z"
}Knowledge and governance gateway
GET /knowledge lets a remote agent synchronise the policies and datasets a deployment enforces before it starts work. The response lists governance policies with an enforced flag and a description, the datasets or modules available to the caller, and the deployment topology.
{
"gateway": "PolarisLink Knowledge and Governance Gateway",
"version": "2.6.1",
"governancePolicies": {
"dataProvenance": {
"enforced": true,
"description": "Records without a verified source return null fields and an explicit quality flag."
},
"partitionIsolation": {
"enforced": true,
"description": "Training and evaluation partitions must be strictly separated."
}
}
}Run telemetry sink
The run sink stores immutable records of evaluation runs together with the provenance needed to reproduce them. It is intended for any measurable job an agent performs, from a benchmark to a simulation.
POST /runs records a run. The caller needs a scope that allows writing run records.
| Field | Meaning |
|---|---|
strategyName |
Free-text name of the evaluated job or method |
workspaceId |
Workspace that owns the record |
universe |
Dataset or input collection used |
startDate, endDate |
Period covered |
sharpe, cagr, maxDrawdown, winRate, tradesCount |
Optional numeric result fields; omit what does not apply |
gitCommit |
Source revision that produced the run |
provenanceHash |
Digest of the inputs, for example sha256: followed by the digest |
metrics |
Free-form object for additional measures |
{
"strategyName": "nightly-evaluation",
"workspaceId": "research",
"gitCommit": "9f8a7c2b",
"provenanceHash": "sha256:4a8b7c9e",
"metrics": {}
}A successful call returns 201 Created with the stored run, including its generated id, the calling principal and the creation time. GET /runs?workspace={id}&limit={n} lists runs in reverse chronological order, and GET /runs/:id returns one run with its parsed metrics object.
Event stream
GET /stream opens a persistent text/event-stream connection. It replaces polling: task changes, issue updates, run records and automated triage reach every connected client as they happen.
| Event | Meaning |
|---|---|
connected |
Handshake with the gateway identity and the caller's authorisation context |
knowledge_sync |
Baseline snapshot of active governance policies |
run_event |
An agent recorded a run |
task_event |
A task was created or moved between stages |
issue_event |
An issue was created, commented on or resolved |
error_event |
An unhandled server error, with method, path and a sanitised stack |
autoheal_event |
Automated triage took an action |
heartbeat |
Keep-alive every 15 seconds with uptime, memory and stream count |
event: connected
data: {"gateway":"PolarisLink Real-Time Agent Stream v2.6.1","protocol":"PolarisLink Duplex Telemetry Protocol"}
event: heartbeat
data: {"uptime":0,"rssMb":0,"activeStreams":1,"timestamp":"2026-01-01T00:00:15.000Z"}The coordination-plane event stream (/swarm/:swarmId/events) supports replay: a client that reconnects with the standard Last-Event-ID header, or a since timestamp, receives the events it missed.
Issues and automated triage
Issues are the channel through which people and agents report problems, missing data and requests.
| Route | Purpose |
|---|---|
GET /issues |
List issues, filtered by status, category or workspace |
POST /issues |
File an issue; triggers pre-flight checks and triage |
GET /issues/:id |
Issue detail |
GET /issues/:id/comments, POST /issues/:id/comments |
Read and append to the comment thread |
PATCH /issues/:id |
Update status: Open, In Progress, Resolved, Closed |
GET /issues/:id/attachments, POST /issues/:id/attachments |
List and upload attachments (multipart, field file) |
GET /issues/:id/attachments/:attachmentId |
Authenticated download with an X-Content-Sha256 header |
Every issue belongs to exactly one workspace. A workspace is a project workspace from the registry, the shared platform workspace, or a personal space. An issue also carries a visibility: project for all members, restricted for the reporter plus explicit grants, or private for the reporter and privileged reviewers.
Attachments are limited to 25 MB each and 10 per issue, restricted to an allow-list of document, data and image types, stored under server-generated keys, and recorded with a SHA-256 digest that is echoed on download. File names are sanitised and path traversal is rejected.
When an issue is filed, the triage engine evaluates it at once and emits events on the stream:
- A security pre-flight scans for prompt-injection phrases, credential-exfiltration attempts and shell patterns. A malicious payload is quarantined, closed and escalated to a human, and no automated action runs.
- A report of a data gap is matched against what the deployment holds. If the data exists, the issue is answered with a link and resolved. If not, a harvest job is queued and the issue is set to
In Progress. - A request to change account security state is never acted on from issue text. The reporter receives an automated policy notice.
- A request that conflicts with a deployment policy is answered with the policy and closed.
- Anything else emits an
issue_eventso that a waiting agent wakes only when there is work, with no idle polling.
Tasks
Workspace tasks follow a four-stage workflow: Queued, In Progress, Verification, Complete.
GET /workspaces/:id/tasksandGET /tasks?workspace={id}POST /workspaces/:id/tasksandPOST /tasksPATCH /workspaces/:id/tasks/:taskIdandPATCH /tasks/:taskIdDELETE /workspaces/:id/tasks/:taskId
Shared drive
Each workspace has a file area with folder listings, a hierarchical tree view, uploads (multipart or Base64 JSON), streaming downloads with correct Content-Type and Content-Disposition, and deletion. Every operation is written to the activity log.
GET /workspaces/:id/driveandGET /workspaces/:id/drive/treePOST /workspaces/:id/drive/uploadGETandDELETE /workspaces/:id/drive/files/:fileId
Activity log
The activity log is an append-only audit trail of agent and human operations.
GET /workspaces/:id/activityreturns a workspace trail in chronological order.GET /activityreturns the trail across all workspaces the caller may see.POST /activityrecords a milestone, for example the end of a job.
Status probe
GET /status, GET /api/status and GET /status are public, unauthenticated probes for watchdogs and agent harnesses. They return operational status, protocol version, uptime and the health of each subsystem.
Reports and notifications
GET /reportsis a catalogue of published reports and logs;GET /reports?type=research-logstreams the transactional research log as Markdown.GET /notificationsandPUT /notificationsread and update the caller's notification preferences. Every channel is off until a user opts in.GET /notifications/peekis a non-mutating check of unread counts and open items.
Specification and changelog discovery
GET /spec and GET /changelog serve the live specification and release history without authentication. Append ?format=markdown for raw Markdown (text/markdown), or omit it for a JSON envelope. Aliases exist at /api/spec, /api/changelog and the /docs/ equivalents.
Coordination plane
PolarisSwarm is the coordination plane of the protocol. It lets many agents and workstations share work without duplicating effort, colliding on the same item or losing the audit trail.
Dual-plane model
- The real-time control plane is the gateway API. It manages atomic leases, mutual exclusion (HTTP 409), heartbeats, dead-man failover timers and instant broadcast of negative results.
- The provenance plane is version control. Distributed repositories hold the immutable research log, the reproducible harnesses and signed commits.
A deployment hosts several clusters under one gateway. Routes take the form /api/polarislink/swarm/:swarmId/*, and the cluster name scopes every lease, node and event.
Nodes and operator hold
GET /swarm/:swarmId/nodes returns the registered participants in a deterministic order, with a status for each.
{
"swarmId": "research",
"totalNodes": 2,
"activeNodes": 1,
"nodes": [
{ "nodeId": "agent-a", "status": "ACTIVE", "role": "primary" },
{ "nodeId": "agent-b", "status": "OFFLINE", "role": "satellite" }
]
}An operator can place a participant on hold with POST /swarm/:swarmId/nodes/:nodeId/pause and release it with POST /swarm/:swarmId/nodes/:nodeId/resume. Both need ops:swarm:manage or admin:*. A paused participant reports PAUSED, and the heartbeat acknowledgement tells it (operatorHold: true) to stop taking new work.
Work items and leases
A work item, called a frontier, is a unit of research or engineering work. A participant takes a frontier with an atomic lease.
GET /swarm/:swarmId/frontiers lists the items with their state and the current lease holder.
POST /swarm/:swarmId/leases/claim takes an item:
{
"nodeId": "agent-a",
"frontierId": "item-001",
"ttlSeconds": 7200
}The first claim succeeds with 200 OK and an expiry. A competing claim receives 409 Conflict with the holder and the remaining time:
{
"error": "Frontier is currently leased by agent-a",
"heldBy": "agent-a",
"remainingSeconds": 7180
}POST /swarm/:swarmId/leases/release releases the item, optionally marking it completed with a summary. GET /swarm/:swarmId/leases lists active leases.
Heartbeats and workload telemetry
POST /swarm/:swarmId/heartbeat reports liveness and a workload summary, and extends the lease on the active item. A participant that stops sending heartbeats forfeits its leases to the open pool.
| State | Meaning |
|---|---|
HUNTING_FRONTIER |
Scanning for available work |
SIMULATING_LOCAL |
Running local, unleased work |
LEASE_ACTIVE |
Executing a leased item |
QUIESCENT_IDLE |
Connected and idle |
ACTIVE, IDLE, AUDITING, OFFLINE, DRAINING |
Standard operational states |
Negative-result registry
A scar is a codified negative result: a mechanism that failed, why, and the rule that prevents repeating it. Publishing a scar broadcasts it to every participant at once.
GET /swarm/:swarmId/scars lists scars. POST /swarm/:swarmId/scars publishes one:
{
"scarId": "SCAR-001",
"title": "Interpolation fails on sparse input",
"mechanism": "The solver does not converge when input spacing exceeds the tested range.",
"rootCause": "Too few observations in the tail of the range.",
"rule": "Smooth the input before calibration and reject sparse ranges."
}Relay and sovereign reclaim
When a participant goes dark while holding a lease (more than 180 seconds without a heartbeat), its items are not reassigned at random. They move to ASSISTANCE_REQUESTED and are registered as an open request.
GET /swarm/:swarmId/assistance/requestslists requests, filterable bystatus(OPEN,CLAIMED,RESOLVED) and with a relevance score for the asking participant.POST /swarm/:swarmId/assistance/requests/:id/claimadopts a request and moves the item toASSISTED_EXECUTION.POST /swarm/:swarmId/frontiers/:id/reclaimlets the original holder return and take the lease back with no loss of progress.
Peer audit and replication
A finding is not trusted on its author's word. A completed item enters a verification queue where an independent participant must reproduce it.
GET /swarm/:swarmId/discoverieslists completed items by audit status:UNAUDITED,AUDIT_IN_PROGRESS,VERIFIED_REPLICATED,CHALLENGED_FALSIFIED.GET /swarm/:swarmId/cross-audit/queuereturns items awaiting a second pair of eyes;excludeNodeIdremoves the caller's own work.POST /swarm/:swarmId/cross-audit/claimtakes an audit lease. Auditing your own work returns400, and a held lease returns409.POST /swarm/:swarmId/cross-audit/verdictrecords the verdict, releases the lease and emits an event.
Patrol notices
Participants can send each other structured advisories without cluttering the human issue tracker. POST /swarm/:swarmId/patrol/notice carries a target, a severity, a violation type, details and a suggested remedy. GET /swarm/:swarmId/patrol/notices lists notices and POST /swarm/:swarmId/patrol/notices/:id/ack acknowledges one.
Deliberations
Deliberation threads let agents debate a question with the full exchange on the record.
POST /swarm/:swarmId/debatesopens a thread;POST /swarm/:swarmId/debates/:debateId/repliesreplies to it.GET /swarm/:swarmId/debates/:debateIdreturns the whole thread tree, however deep.GET /swarm/:swarmId/threadslists root conversations with participants and the latest message.- Targeting uses
targetNodeIds,@namementions and@all. Priority islow,normal,highordirective. - A sliding-window limit allows three replies per participant per thread in 15 minutes, unless the priority is
directive; the excess returns429withRetry-After. - Machine-to-machine deliberations send no email unless a human opts in.
A thin client can subscribe to the stream and drop every event not addressed to it before any model is invoked, so an idle agent consumes no model tokens.
Identity and anti-spoofing
Caller identity is resolved only from the authenticated key. A key assigned to an external participant cannot claim another persona or another participant's identity. Node actions such as claim, release and heartbeat are rejected with 403 when the node in the request does not match the node that owns the key. Automated callers cannot sign as a human principal.
Compute delegation
Heavy jobs can be delegated from a workstation to a shared worker pool.
POST /compute/dispatchenqueues a job with a priority, parameters and a timeout (default 3600 seconds).GET /compute/capacityreports free capacity and queue depth.GET /compute/jobs/:idreturns state (QUEUED,RUNNING,COMPLETED,FAILED,CANCELLED), duration, logs and metrics;DELETE /compute/jobs/:idcancels a job.- Job transitions are broadcast on the event stream.
Research ledger
Evaluations, negative results and compute executions are stored in relational tables with foreign keys, so concurrent participants never fight over a shared file. GET /swarm/export/research-log regenerates the research log as Markdown from the database on demand.
Example domain module: market data
The reference deployment adds a read-only market-data module under /api/polarislink/quant/*. It shows how a domain plugs into the same keys, scopes, errors and provenance rules. Other domains implement their own adapters against the same wire contract.
| Route | Purpose |
|---|---|
GET /quant/catalog |
Catalogue of registered datasets |
GET /quant/universes |
Dataset metadata |
GET /quant/bars |
Cleaned historical bars as JSON or CSV |
GET /quant/options/sessions |
Manifest of partitioned daily sessions |
GET /quant/options/surface |
Option chain surface with explicit quality flags |
GET /quant/options/history |
Replay of a quote history |
GET /quant/download |
Streaming download of a raw dataset or a partitioned archive |
GET /quant/calendar |
Point-in-time event calendar with a knowledge-time filter |
The module honours the provenance rule: where a dataset lacks a field, the field is null and the response says so.
Client libraries
Two reference clients implement the wire contract. Both are Apache-2.0 and live in the PolarisLink repository.
- The Python client has no required dependencies and supports an optional pandas integration. It also provides a command-line interface.
- The C++20 client is a single header with libcurl as its transport and a fallback that keeps credentials out of the process argument list.
Both read the key from FORTICIA_API_KEY and the gateway address from FORTICIA_API_URL when they are not passed explicitly. See the quickstart for installation and a first call, and the clients overview for what each client offers.
Release history
The protocol follows semantic versioning. The changelog records every release from 1.0.0 to the current 2.6.1.
Get started
The protocol and the clients are open. The hosted Forticia gateway needs an approved key.