Story 009: The magazine publishes itself — a three-node blogging pipeline about Bielik, conducted by Arca¶
Summary¶
As the editor-in-chief of a small, opinionated technical blog about Polish language models, I want my three Orbiplex nodes — each in a different role — to run the publishing cycle for the Bielik language model on their own: the first does research and writes a draft, the second illustrates it, the third proofreads and pushes the finished material to the branch that Netlify automatically deploys to the Hugo-built site.
My job as the operator is to watch the Arca audit channel and see which step we are on, who signed what, and what landed in git — without logging into a CMS panel, without manually gluing API calls, and without one giant script that "does everything".
This story is a direct rendering of the seqnote "The magazine publishes itself" into a minimal technical flow. The seqnote describes the vision: an editorial team as a swarm of cooperating, specialised nodes with shared memory (Memarium), instead of a single "AI panel" stitched together from services. Here we step one floor down: three concrete nodes, one concrete topic (the Bielik model), one concrete workflow in Arca, one git repository tracked by Netlify.
Current Baseline Used by This Story¶
The story relies on:
- Proposal 029 (Workflow Template Catalog) — definition of
WorkflowDefinitionand the template catalog: the five steps of this story are named step templates, parameterised by topic (Bielik) and target repository. - Proposal 033 (Workflow Fan-Out and Temporal Orchestration) — temporal primitives (timeout, retry, deadline) for the research, illustration, review, publish, and verification steps. Host-managed fan-out is not used here; Arca uses an ordinary service-order DAG: after draft completion it can dispatch illustration and editorial review without waiting for either result, then publish waits for both branches.
- Proposal 019 (Supervised Local HTTP/JSON Middleware Executor) — each of the three nodes runs its specialised LLM modules (research, illustration, review) as supervised middleware modules that publish active offers for declared task types.
- Story 000 — node identity and participant identity, used by
service offers and service orders once Arca selects a provider by
task_type. - Story 008 — the pattern of a record signed by a participant key
(
PrimaryParticipant) as an auditable unit. Here the same mechanism is used by git commits: every commit is optionally signed by the key of the node that produced it, and the trace of that signature lands in the author node's Memarium. - Host-owned workflow step-completion read models and local Agora
monitoring — each daemon that executes a step accepts its own
workflow.step.completedrecord and exposes it through/v1/workflows/runs/{workflow_run_id}/steps/completed. Arca may publish the finalworkflow.completedmonitoring fact to local Agora. Each node still has its own, local Memarium; editorial memory continuity is emergent and is reconstructed from explicit pointers, not from a single shared store.
Transport Stratification (architectural decision)¶
The story explicitly separates two channels:
- Data plane = git. The article body (markdown), illustrations,
editorial corrections — everything that constitutes "the work" — is
carried solely through the git repository
(
drafts/bielik-…↔publish/main). Bytes do not enter Arca, do not enter Agora, and do not enter Memarium as primary material. - Control plane = Arca + Agora. Between steps we pass pointers:
branch name, commit SHA, paths of added files, Memarium record
identifier. These are small, control-shaped, auditable data — they
fit naturally into
input_from_step(proposal 029) and into anagora-record.v1record (story 008).
Therefore: we introduce no new artefact transport. Proposal 042
(INAC) and the memarium-blob.v1 schema are the natural extension
direction whenever the editorial team wants to exchange artefacts
unfit for git (large binary assets, confidential briefs) — but this
story deliberately does not use them, to avoid adding code.
Local action boundary: Sensorium OS connector¶
The story-specific role adapter does not spawn shell commands directly. In the
official reference profile, the roles bielik-researcher, illustrator,
editor-in-chief, git-signer, and git-publisher are implemented as
configuration-driven json_e_flow middleware instances that own only the
declarative task glue: render a Sensorium directive, call Sensorium, write a
Memarium fact, publish a workflow-step completion fact, and return a
pointer-sized service response. Any local operating-system action required to
carry out those tasks is mediated through sensorium-core and an allowlisted
Sensorium OS connector.
This includes git fetch/checkout/read paths, file writes in the local
worktree, signed commits, guarded pushes, narrow public-source
fetching when a source is represented operationally as an OS action,
and the generative work itself — draft composition, image
generation, language proofreading — invoked as allowlisted OS
connector scripts that wrap the node's local model or tool. Role
modules do not run language models, diffusion models, or shell
utilities in-process; each unit of work is a named action_id with a
declared script path, parameter schema, timeout, cwd, environment,
output caps, artifact capture, and incidental effects — all enforced
at the connector boundary. The role adapter remains a consumer of
sensorium.directive.invoke; it does not receive sensorium.connector.invoke
grants and does not bypass sensorium-core.
Connector-side validation failures use the shared
sensorium-os-error-codes.v1 vocabulary and, when they reject an
otherwise executed action result, are also represented as
ai.orbiplex.sensorium/action-invalid observations so operator audit
does not depend on seeing a transient HTTP response.
This does not change the transport stratification above: article bytes still flow through git, not through Arca, Agora, or Memarium. Sensorium's OS connector is an enaction boundary for local operations, not an artefact transport and not a parallel workflow engine.
The OS connector implementation remains program-agnostic. The fact that this
story uses actions with Git-shaped names does not mean the connector knows
Git. The operator-authored action catalog maps each action_id to a concrete
script or command invocation, argv shape, environment, class envelope, result
contract, and optional scoped signing lane. Git semantics — checkout, commit,
fast-forward, push, and how the commit payload is signed — live in those
configured scripts and in the JSON-e role adapter configuration that calls
them, not in the connector code.
The same rule applies to language-model use in this story. llm-research,
draft-author, proofreading, and any other model-assisted work are ordinary
workflow tasks that reach the model through an allowlisted OS connector action
wrapping a local script or tool. This gives the reference implementation a
working model path without adding model-specific code to Arca, Dator,
Sensorium-core, or the OS connector. Inquirium is the intended future organ for
that model path: the workflow names the task, the role adapter invokes a declared
capability/action, and model-specific prompt construction, runtime choice,
provider credentials, batching, and output shaping live in Inquirium/model-runtime
or a compatibility wrapper, not in the orchestration layer.
Action classification and authorization come from proposal 048.
This story does not introduce its own trust model, its own allowlist
format, or its own signing ceremony for OS connector scripts. Each
action used here belongs to one of the classes defined in 048, and
the connector's action catalog is authorized via the sidecar-signature
mechanism from that proposal. Actions that need participant signatures, such
as signed git commits, declare a bounded signing lane in that same catalog
(for example one signature in the git.commit.v1 domain); sensorium-core
and the host signer authorize that narrow signer grant for the spawned script.
The OS connector still only runs the configured process and never becomes a
signing oracle. In the fresh-install demo path, the
five story-009 scripts ship as factory defaults of the Sensorium
OS connector; after first start, the node materializes them into the
active configuration area and the node itself emits a node-signed
sidecar over the merged effective configuration — so the demo runs
without any operator signing ceremony, unless the operator edits the
connector configuration, at which point the standard operator
admittance flow from 048 applies.
The story assumes the three nodes (A, B, C) are running and that
each runs Orbiplex Dator as its supply-side marketplace facade.
Dator on each node publishes the node's active service-offer.v1
records for the relevant task_type values, accepts incoming service
orders from Arca on behalf of the local role capability provider, and enforces
the node's bounded acceptance posture (queue depth, concurrency, refusal when
the local role capability is not ready). In the phase-0 mapping,
task_type is projected onto service/type in the offer catalog. The
git repository and Netlify configuration exist; they are an external
artefact on which this story merely performs agreed operations.
Dator is not an executor of editorial semantics and not an actor on
the local operating system: it is the responder-side bridge between
Arca's dispatch and the specialised role capability provider named
below. In the reference profile that provider is json_e_flow, not a separate
HTTP daemon. All editorial work is expressed by the role adapter configuration
and the allowlisted scripts; all local OS actions are mediated through
sensorium-core and the allowlisted Sensorium OS connector.
For this demo, every offer published by every node's Dator has
price = 0 ORC (free). This is a deliberate simplification: the
subject of this story is editorial orchestration and local enaction,
not settlement. A zero price keeps the flow on a single path
(service-offer.v1 → service-order.v1 → accept → role capability →
result) without wiring holds, escrow, or ledger updates into the
story. Variants with non-zero pricing, negotiation, or settlement are
a separate story.
The reference Dator profile expresses this local shortcut with
confirmation_mode: "automatic". That value is profile-local vocabulary: it
means the zero-price story path uses the deterministic fallback "zero price path
does not require confirmation." It is not an official on-wire
confirmation/mode value; published service-offer.v1 records should omit
confirmation/mode or use one of the official confirmation modes after local
normalization.
Cast and Scene¶
- Node A — Bielik Researcher. Operator:
participant:did:key:z6MkA…. Runs Dator which publishes its active offers for task types:llm-research,draft-author,git-commit-draft, and accepts Arca-dispatched service orders for them. Has access to Sensorium (connectors to external sources: arXiv, GitHub repositories, the Bielik mailing list, news feeds) and to the Sensorium OS connector for allowlisted local repo and source-fetch actions. The researcher module still receives admitted observations as facts; when it needs to touch the local worktree or invoke a source-fetching command, it does so through an Arca-mediatedsensorium.directive.invokepath. Node A also has access to its own, local Memarium which retains this node's previous Bielik articles and observed facts published by nodes B and C (illustration decisions, editorial rulings, rejected variants returning from review). - Node B — Illustrator. Operator:
participant:did:key:z6MkB…. Runs Dator which publishes its active offers for task types:draft-read,image-generate,image-place,git-commit-illustrated, and accepts Arca-dispatched service orders for them. Has a local diffusion model and its own local Memarium in which it keeps its aesthetic policy (palette, hero image typography, allowed styles) — a policy fed by facts published by node C (acceptances and rejections of illustrations from previous runs) and by its own generative decisions. Git checkout, worktree writes, commit actions, and the image generation itself are mediated through the Sensorium OS connector: the diffusion model is wrapped as an allowlisted connector script, and the illustrator role module owns only the semantic decisions (what to depict, where to place each image, which aesthetic policy to apply) — not the in-process execution of the model. - Node C — Editor-in-Chief. Operator:
participant:did:key:z6MkC…. Runs Dator which publishes its active offers for task types:draft-read,editorial-review,guardrails-as-code,git-push-publish, and accepts Arca-dispatched service orders for them.git-push-publishis advertised by Dator only on node C; no other node's Dator publishes that offer. Holds the only key authorised to push to the branch tracked by Netlify (publish/main). Has the editorial line rules installed as code (proposal 026 §Guardrails-as-code — a non-functional contract at node level, not a content-schema). Review checkout, signed commit, merge, and push actions are mediated through the Sensorium OS connector, withgit-push-publishallowlisted only on node C. - Arca, as a workflow module running on one of the nodes (in this story: on node A as host, but that is just the engine's location — Arca is agnostic about where the participants of the defined steps physically live; it finds them through task-type offer lookup).
- The git repository
git@bielik-blog:orbiplex/bielik-blog.gitwith Hugo structure (content/pl/log/<year>/,content/en/log/<year>/,static/img/posts/,config.toml). Thedrafts/*branch is for drafts and working versions; thepublish/mainbranch is the only one Netlify deploys. - Three local Memariums — one per node. Each stores its own facts (what the given node itself produced or decided) and any observations it is explicitly allowed to import through workflow, local relay, or INAC contracts. "Editorial memory" as a whole is an emergent view composed of these three Memariums plus workflow step-completion pointers; it does not exist as a single shared store nor a single authoritative back-end. Consistency is achieved through append-only records, not through shared state.
The topic of this story is the publishing cycle: "What's new with Bielik" — a periodic article summarising changes, releases and community signals around the Bielik model on a biweekly cadence.
Operator Setup From Scratch¶
This section is an operational runbook for an operator who wants to bring the story up from an empty environment. It deliberately keeps the Git, model, and publishing semantics in operator-authored scripts and configuration. Orbiplex components provide orchestration, capability mediation, storage, audit, and local supervision; they do not learn the semantics of Git, Netlify, Bielik, or a particular LLM.
There are three supported setup shapes:
- Reference skeleton on one host. One daemon starts Arca, Dator,
in-process JSON-e role flows (
story009.json-e-flow.roles),sensorium-core,sensorium-os, Memarium, and Agora locally. This is the fastest path for development and for verifying the contract. - Persistent one-laptop operator pack. Three durable node profiles live on one operator machine with distinct control, WSS, and Node UI ports. This uses the same three-node topology as the production story while keeping the setup local and repeatable.
- Three-computer editorial deployment. Node A, B, and C each run
their own daemon, Dator, Sensorium, Sensorium OS connector, and
local Memarium. Arca may run on node A. Only node C advertises and
authorises
git-push-publish.
Production Target Decisions¶
The production-oriented variant of story-009 uses these decisions:
- Git data plane: the editorial repository is a real GitLab repository,
reachable by the operator environment as
git@bielik-blog:orbiplex/bielik-blog.git. Thebielik-blogSSH host alias and private deploy key are operator-managed machine configuration, not Orbiplex configuration and not repository content. - Publishing branch:
publish/mainremains the only publication branch. Node C is the only node allowed to push it. - Hugo content layout: Polish articles live under
content/pl/log/<year>/; English translations live undercontent/en/log/<year>/. - Deployment target: Netlify, or an equivalent deployment system, watches
the publishing branch. The verifier checks the public site URL, expected under
https://bielik.orbiplex.ai/, for examplehttps://bielik.orbiplex.ai/pl/log/2026/bielik-13B-instruct/. Netlify setup is external infrastructure and is not automated by this story. - Operator model: one operator manages the three nodes and signs the relevant local authority artifacts.
- Role provider model: story-009 officially uses
json_e_flowrole providers. The former HTTP-local role-module shape is legacy/deprecated for this story. - Model wrappers: real LLM or diffusion use is deliberately outside the Orbiplex core. The allowlisted script may use an OpenRouter-compatible API URL and optional API key; if those are unset or empty, it must fall back to static deterministic text.
- Publish approval:
git-push-publishis operator-gated. The UI should show anAwaiting acceptanceitem containing the requesting component, requested action, action content digest/summary, and[Sign]/[Reject]actions. The accepted path is an operator signature over the approval artifact. - Discovery trust: production discovery is capability-passport and issuer
policy based. Harness
trusted_node_idsremain an override, not the final trust model. - Provider choice: Arca may select providers automatically, but the operator must be able to override the selected provider for a run.
- Completion view: the production UI aggregates per-node
/v1/workflows/runs/{workflow_run_id}/steps/completedcontrol read models. There is no Agora-backed synthetic step-completion store. - Monitoring fact: Arca still emits
workflow.completedas a local Agora monitoring/history fact. - Audit model: the production audit bundle is composed from Memarium facts, workflow step-completion records, signer traces, and an exportable run bundle.
- Retention: Memarium facts are retained indefinitely. Sensorium artifacts use TTL. Script stdout/stderr artifacts are retained for one day by default.
Prerequisites¶
Before installing Orbiplex on the machines, prepare:
- Three hostnames or machine labels:
node-a,node-b,node-c. - One Git repository with a Hugo-compatible layout. For the production target,
the remote is
git@bielik-blog:orbiplex/bielik-blog.git, Polish posts live undercontent/pl/log/<year>/, English translations live undercontent/en/log/<year>/, and the publishing branch ispublish/main. - A branch policy for the publish branch. In the reference deployment,
this may be a local bare repository with a
pre-receivehook. In a real deployment, use repository permissions or hooks so only node C can updatepublish/main. - A Netlify site or equivalent deployment target watching only
publish/main. Netlify is not part of the Orbiplex control plane; it is an external observer of the Git data plane. - Participant/operator identities for the three nodes. A fresh demo can generate these through the Node UI; a real deployment should import or create them intentionally and store recovery material.
- Scripts or wrappers for every local action. The bundled reference scripts are deterministic fixtures; real LLM, image, review, and Git operations should be installed as allowlisted OS connector scripts.
Software Required on Every Computer¶
Install the same baseline software on each node host:
git --version
python3 --version
cargo --version
Required packages:
- Rust toolchain with Cargo, sufficient to build the daemon, Node UI, and the Rust middleware services.
- Python 3.11 or newer.
- Python
jsonschema, used by the Sensorium reference modules for JSON Schema validation at the boundary. - Git CLI.
- Platform build tools (
clang/Xcode Command Line Tools on macOS, equivalent compiler toolchain on Linux).
Typical development bootstrap:
cd node
python3 -m pip install --user jsonschema
cargo build -p orbiplex-node-daemon -p orbiplex-node-ui -p orbiplex-node-agora-service
cargo test -p orbiplex-node-daemon --test story_009_sensorium_role_dispatch
If a machine is expected to run real LLM or image generation tools,
install those runtimes outside Orbiplex and expose them through
operator-authored wrapper scripts. The role adapters and Sensorium OS
connector should still see only an action_id, a script path, JSON
parameters, and a JSON result contract.
Base Node Materialisation¶
On each computer, create a separate data directory and materialise the factory configuration:
cd node
export ORBIPLEX_NODE_DATA_DIR="$HOME/.orbiplex-story009/node-a"
cargo run -p orbiplex-node-daemon -- materialize-config --data-dir "$ORBIPLEX_NODE_DATA_DIR"
cargo run -p orbiplex-node-daemon -- check-config --data-dir "$ORBIPLEX_NODE_DATA_DIR"
Start the node after the overlay config is in place:
cargo run -p orbiplex-node-daemon -- run --data-dir "$ORBIPLEX_NODE_DATA_DIR"
If the deployment uses the operator control helper, the equivalent operator command is:
python3 tools/orbiplex-node-control.py --data-dir "$ORBIPLEX_NODE_DATA_DIR" up
The up helper starts the daemon and, when node_ui.start_with_node
is true, the co-located Node UI. In a foreground-only development
run, start Node UI from a second terminal if it is not started by the
helper.
Use node-b and node-c data directories on the other machines. If
all three nodes are simulated on one host, use distinct data
directories and distinct loopback ports in each overlay. If the nodes
run on separate machines, the bundled loopback ports may remain the
same because they are local to each host.
For the persistent one-laptop profile, the operator helper in the node repository creates:
$HOME/.orbiplex/bielik-blog-A
$HOME/.orbiplex/bielik-blog-B
$HOME/.orbiplex/bielik-blog-C
$HOME/.orbiplex/bielik-blog-data
bielik-blog-data owns the shared Git clone, rendered staging profiles, local
WSS peer-discovery material, audit exports, and logs. The helper keeps daemon
startup and Node UI startup separate so all three UIs can run at once:
- node A UI:
http://127.0.0.1:47990 - node B UI:
http://127.0.0.1:48090 - node C UI:
http://127.0.0.1:48190
In this local shape, node A should discover role providers through node B and
node C Seed Directory endpoints by default, not by querying only itself. The
reference helper therefore renders node A with Seed Directory endpoints for
node B and node C. Missing Sensorium OS action-catalog sidecars may be reported
as awaiting_operator_signature during initialization before the daemons are
started; daemon startup materializes them as node-self signed bootstrap
sidecars. A present-but-stale sidecar is not auto-replaced and requires an
explicit operator decision in Node UI. In that case the daemon may enter
Local Readiness Gate (proposal 050): control/UI endpoints stay live, but
story middleware is not started until the operator signs or rejects the changed
catalog.
The local operator pack enables local_readiness.auto_ready for fresh dev
profiles. If a profile contains exactly one recently-created plaintext
participant key, the daemon may automatically create the local node-operator
binding, sign pending local readiness sidecars, and issue the required local
capability passports for the story modules. The attempt can run at startup or
immediately after the first participant is created/imported while the daemon is
already waiting in Local Readiness Gate. A successful auto-ready pass still
stops at Local Readiness Gate with status: "restart_required"; the operator or
launcher performs one restart so middleware starts from a clean lifecycle
boundary.
The daemon writes operator-edited config fragments under:
<data_dir>/config/*.json
Bundled middleware config is seeded there when seed_config is true.
Operator fragments should use higher lexical names such as
70-story009.json so they override generated defaults without
editing factory files.
Common Node Configuration¶
Each node participating in the story needs these components enabled:
dator— publishes local zero-price offers and routes accepted service orders to the role capability provider.middleware_json_e_flow_services— owns the story-specific role adapter configuration forrole.bielik-*.execute; this is the official Story-009 path.sensorium_core— mediates Sensorium directives, validates parameters, records outcomes, stores observations, and dispatches to connectors.sensorium_os— executes allowlisted scripts or processes. It must never be granted directly to consumers.agora_service— local relay used for Sensorium observation topics and the finalworkflow.completedrecord.- Memarium — enabled as a host capability in the daemon; every node keeps its own local store.
Minimal overlay shape:
{
"agora_service": {
"enabled": true,
"relay_id": "story009-node-a-local",
"role": "local",
"relay_domain": "node-a.local"
},
"sensorium_core": {
"enabled": true,
"publish_to_agora": true,
"agora_base_url": "http://127.0.0.1:47991"
},
"sensorium_os": {
"enabled": true,
"allowed_workdirs": ["/srv/orbiplex/story009/blog-bielik"],
"allowed_script_roots": ["actions"]
},
"middleware_json_e_flow_services": {
"story009-role-bielik-researcher-json-e-flow": {
"module_id": "story009.json-e-flow.roles",
"bindings": {
"role_capability_id": "role.bielik-researcher.execute",
"action_id": "story009.draft.compose"
}
}
}
}
The JSON example above is intentionally partial: a real json_e_flow service
entry also declares component_id, template_id, context projection, helper
profile, allowed host calls, limits, and static flow steps. The operator pack in
node/tools/acceptance/story-009-operator renders the full entries.
The allowed_workdirs entry must point at the local checkout used by
that node. The reference defaults intentionally ship with an empty
workdir allowlist, so OS actions fail closed until the operator
chooses the workspace.
For private repositories, do not rely on ambient HOME. The OS
connector intentionally starts scripts with a minimal environment.
Declare any needed credential helper, SSH command, deploy key path, or
non-interactive Git setting in the action catalog entry. At minimum,
story actions should set:
{
"GIT_TERMINAL_PROMPT": "0",
"GIT_ASKPASS": "/bin/true"
}
For the GitLab target above, the operator machine may provide an SSH alias such
as bielik-blog in ~/.ssh/config. If the OS action catalog needs to pin that
route explicitly, set a non-interactive GIT_SSH_COMMAND, for example:
{
"GIT_SSH_COMMAND": "ssh -F /home/orbiplex/.ssh/config -o IdentitiesOnly=yes"
}
Private deploy keys are deployment secrets. They may be installed on trusted operator/developer machines, but they must not be committed to Orbiplex or to story content repositories.
Node A Configuration¶
Node A hosts the operator-facing Arca workflow in this story and owns the drafting role.
System setup:
- Clone or initialise the editorial repository checkout under node A's allowlisted workdir.
- Grant node A read/fetch access to the origin and permission to push draft branches if the workflow uses remote draft branches.
- Do not grant node A permission to push
publish/main.
Dator offers:
- Keep or declare
draft-author. - In a fuller deployment, add split offers for
llm-research,git-commit-draft, or other specialised task types only if the workflow template references them explicitly. - Remove
git-push-publishfrom node A's active Dator offer catalog.
Sensorium OS action catalog:
- Keep actions needed by the drafting path, such as
story009.draft.compose. - Add real LLM/source-fetch wrapper actions as needed.
- Do not include a publish action that can update
publish/main.
UI/operator steps on node A:
- Start the daemon and Node UI.
- Create or import the participant identity for node A.
- Confirm the node-operator binding if the UI asks for it. In a fresh auto-ready profile this may already be materialized by the daemon, in which case the UI shows the restart-required continuation instead of a manual binding action.
- Inspect the Dator offers and verify that
draft-authoris active. - Import or create the
bielik-biweekly-publish.v1workflow template. - Start the workflow from the template with repository, branch, topic, and cadence parameters.
Node B Configuration¶
Node B owns illustration and image placement.
System setup:
- Clone the editorial repository under node B's allowlisted workdir.
- Grant read/fetch access to the origin and write access only to the branches needed for illustration commits.
- Install the image-generation wrapper or deterministic fixture script under an allowlisted script root.
Dator offers:
- Keep or declare
image-place. - If the workflow later splits image generation from placement, add
image-generateandgit-commit-illustratedas separate offers. - Do not advertise
git-push-publish.
Sensorium OS action catalog:
- Keep actions needed by the illustration path, such as
story009.image.place. - Add the real image-generation wrapper action if this node uses a model instead of the deterministic fixture.
- Do not include the publish action.
UI/operator steps on node B:
- Start the daemon and Node UI.
- Create or import the participant identity for node B.
- Confirm or install the module grants needed by Dator, Sensorium, Sensorium OS, and Memarium.
- Inspect Dator and verify that
image-placeis active and priced at0 ORCfor the demo. - Inspect Sensorium OS and verify that only node B's intended actions are present in the effective catalog.
Node C Configuration¶
Node C owns editorial review, publication verification, and the only publish authority.
System setup:
- Clone the editorial repository under node C's allowlisted workdir.
- Install credentials or repository policy allowing node C, and only
node C, to update
publish/main. - Configure the publish branch protection or hook before running the workflow. The negative path is part of the story: a node A/B publish attempt must fail.
Dator offers:
- Keep or declare
git-push-publish. - Keep or declare
publication-verifierif verification is executed by node C. - Do not advertise drafting or illustration offers unless the operator deliberately wants a single-node fallback demo.
Sensorium OS action catalog:
- Keep
story009.review.publish. - Keep
story009.publication.verify. - Include the bounded signing lane for commit-producing actions:
signing.allowed_domains = ["git.commit.v1"]. - Treat the publish action as operator-gated outside the demo path. In production, an operator-edited catalog should be authorised by an operator signature rather than relying only on node-signed factory bootstrap.
- Require an explicit operator approval for each
git-push-publishrequest in production. The UI should presentAwaiting acceptancewith the requesting component id, action id, target branch, content digest/summary, and[Sign]/[Reject]buttons.[Sign]produces the approval signature consumed by the publish action;[Reject]records a structured refusal.
UI/operator steps on node C:
- Start the daemon and Node UI.
- Create or import the participant identity for node C.
- Confirm or install the grants for publishing, signing, Sensorium, and Memarium.
- Inspect Dator and verify that
git-push-publishis active only here. - Inspect Sensorium OS and verify that the publish action points to the intended script, hash, workdir, branch, and signing domain.
Action Catalog and Sidecar Authorisation¶
The action catalog is the operator's declaration of what local actions exist. For story-009, the reference actions are:
story009.draft.composestory009.image.placestory009.review.publishstory009.publication.verify
Each action declaration should include:
action_idscript_pathsha256for the script file when the action is not purely throw-away development workparameters_schemaresult_schemalimitscwd_paramenvresult_contract.pointer_fieldsconnector_incidental_effects- optional
signing.allowed_domains
Fresh factory defaults may be node-signed during bootstrap. Once the
operator edits the catalog, the effective catalog must be re-authorised
through the sidecar mechanism described in proposal 048. If
require_action_catalog_signature is enabled and the sidecar hash does
not match the effective catalog, sensorium-os must refuse to expose
the action. The check is not only a startup ceremony: dispatch must
also re-read or revalidate the active sidecar so an operator change on
disk cannot leave a stale in-memory authorization silently active.
Result contracts are deliberately strict about pointer fields. If an
action declares result_contract.pointer_fields, every listed field
must be present in the script's JSON result. Missing pointer fields are
reported as result-pointer-missing, and schema mismatch is reported
as result-schema-invalid; both produce an action-invalid observation
for audit symmetry.
UI Checklist Before Running the Workflow¶
In the Node UI on each node:
- Open Identity and create or import the participant key.
- Confirm that the node identity and participant identity are present.
- Open Components and confirm that Dator, Sensorium Core, Sensorium OS, Story 009 Roles, and Agora Service are running where expected.
- Open the component or config view and inspect the effective
sensorium_os.action_catalog. - Open the Dator/offers view and confirm the node-specific offers.
- On node A, open the workflow templates view, instantiate
bielik-biweekly-publish.v1, and start the run. - Watch the workflow run view. Each step should carry only pointer
fields: branch, commit, path,
memarium_record_id, and Sensorium outcome/observation identifiers. - Open the JSON-e flow middleware view when diagnosing role adapters. It should show the role capability, allowed host calls, trace retention policy, recent trace summaries, and per-step request/response digests without exposing raw secret-bearing context.
- When the publish step reaches the C7 gate, confirm that the UI shows
Awaiting acceptancewith the requesting component, action id, target branch, and content digest/summary; choose[Sign]only if the request is expected. - After completion, inspect Agora and verify a
workflow.completedrecord with links to the three commit facts plus the publication verification fact. - Inspect each local Memarium. The facts should be append-only and local to the node that performed the step.
- Open the step-completion aggregation view, or run
story-009-step-completions.py --expect-complete, and verify that all five expected step ids are present exactly once.
CLI Smoke Test¶
Before using real machines, run the reference skeleton from the node workspace:
cd node
python3 tools/acceptance/story-009-reference-skeleton.py
cargo test -p orbiplex-node-daemon --test story_009_sensorium_role_dispatch -- --nocapture
The smoke test should show:
- a draft commit,
- an illustration commit,
- an accepted review/publish commit,
- a rejected review probe,
- a publication verification fact,
- Sensorium outcomes and observations,
- and reconstruction data derived from workflow pointers and Memarium fact identifiers.
If this passes but a real node fails, the usual causes are configuration-layer issues: missing workdir allowlist, stale action catalog sidecar, absent Git credentials, wrong Dator offer set, or a publish action accidentally enabled on the wrong node.
Example Sensorium OS Connector Scripts¶
The Sensorium OS connector does not call a Python function in-process.
It starts an allowlisted program declared in the action catalog. For
os.script.run, the reference connector invokes the script as:
<interpreter> <script_path> --params-json '<json object>'
The script contract is intentionally narrow:
- input is the JSON object passed in
--params-json; - stdin is not used;
- stdout must contain one JSON object matching the action's
result_schema; - stderr is diagnostic text and must not be parsed as the result;
- non-zero exit status means the action failed before producing a valid result;
- every field listed in
result_contract.pointer_fieldsmust be present in the JSON result.
The examples below are deliberately domain-local. Sensorium OS does not know what "article", "editor", "Bielik", or "published" means. Those meanings live in the allowlisted script, its parameter schema, its result schema, and the role module that interprets the result.
Example 1: Content Producer Script¶
This shape fits an action such as story009.draft.compose. The script
receives simple JSON parameters, writes placeholder article content to
a file inside the allowlisted workdir, and returns pointers plus small
metadata. The article bytes stay in the filesystem/Git data plane; the
workflow sees only pointers.
#!/usr/bin/env python3
"""Minimal story-009 content producer for a Sensorium OS action."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import urllib.request
from pathlib import Path
from typing import Any
def parse_params() -> dict[str, Any]:
parser = argparse.ArgumentParser()
parser.add_argument("--params-json", required=True)
return json.loads(parser.parse_args().params_json)
def sha256_text(text: str) -> str:
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
return f"sha256:{digest}"
def maybe_generate_with_openrouter(prompt: str) -> str | None:
api_url = os.environ.get("OPENROUTER_API_URL", "").strip()
api_key = os.environ.get("OPENROUTER_API_KEY", "").strip()
model = os.environ.get("OPENROUTER_MODEL", "").strip() or "openrouter/auto"
if not api_url:
return None
request_body = json.dumps(
{
"model": model,
"messages": [
{"role": "system", "content": "Write a concise Hugo blog draft."},
{"role": "user", "content": prompt},
],
}
).encode("utf-8")
headers = {"Content-Type": "application/json"}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
request = urllib.request.Request(api_url, data=request_body, headers=headers, method="POST")
with urllib.request.urlopen(request, timeout=60) as response: # nosec: operator-configured endpoint
payload = json.loads(response.read().decode("utf-8"))
return payload["choices"][0]["message"]["content"]
def main() -> None:
params = parse_params()
repo = Path(params["repo"]).resolve()
draft_path = Path(params.get("draft_path") or "content/pl/log/2026/bielik-draft.md")
title = str(params.get("title") or "What is new with Bielik")
topic = str(params.get("topic") or "Bielik")
output_path = repo / draft_path
output_path.parent.mkdir(parents=True, exist_ok=True)
prompt = f"Write a short Hugo markdown draft about {topic}. Title: {title}."
generated = maybe_generate_with_openrouter(prompt)
body = generated or f"""{topic} has seen a careful week of small, useful improvements.
The model ecosystem is becoming easier to test, easier to discuss, and easier to reuse.
This placeholder paragraph stands in for a real local LLM wrapper or editorial script.
"""
content = f"""---
title: "{title}"
draft: true
tags: ["bielik", "llm", "polski"]
---
{body}
"""
output_path.write_text(content, encoding="utf-8")
result = {
"outcome": "ok",
"draft_path": str(draft_path),
"content_sha256": sha256_text(content),
"content_chars": len(content),
"summary": {
"lang": "en",
"text": "Placeholder Bielik draft content was produced.",
},
}
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
if __name__ == "__main__":
main()
Minimal action-catalog expectations for this producer:
parameters_schemarequires at leastrepoand may acceptdraft_path,title, andtopic;result_schemarequiresoutcome,draft_path,content_sha256, andcontent_chars;result_contract.pointer_fieldsshould include at leastdraft_pathandcontent_sha256.
Example 2: Editor / Fulfillment Validator Script¶
This shape fits an action such as story009.publication.verify or a
lightweight editorial gate before publish. The script does not know
Arca. It reads the file indicated by the parameters, applies one
simple rule, and returns a structured decision. Here the rule is
intentionally trivial: the text is fulfilled only if its length is at
least min_chars.
#!/usr/bin/env python3
"""Minimal story-009 editor/fulfillment validator for a Sensorium OS action."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
def parse_params() -> dict[str, Any]:
parser = argparse.ArgumentParser()
parser.add_argument("--params-json", required=True)
return json.loads(parser.parse_args().params_json)
def main() -> None:
params = parse_params()
repo = Path(params["repo"]).resolve()
draft_path = Path(params["draft_path"])
min_chars = int(params.get("min_chars") or 600)
text = (repo / draft_path).read_text(encoding="utf-8")
char_count = len(text)
fulfilled = char_count >= min_chars
result = {
"outcome": "ok" if fulfilled else "rejected",
"editorial_decision": "accepted" if fulfilled else "rejected",
"reviewed_path": str(draft_path),
"content_chars": char_count,
"verification": {
"status": "fulfilled" if fulfilled else "not_fulfilled",
"kind": "content-length",
"min_chars": min_chars,
"actual_chars": char_count,
},
"rejection_reason": None if fulfilled else "content is shorter than the configured minimum",
}
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
if __name__ == "__main__":
main()
Minimal action-catalog expectations for this validator:
parameters_schemarequiresrepoanddraft_path, and may acceptmin_chars;result_schemarequiresoutcome,editorial_decision,reviewed_path,content_chars, andverification.status;- the role module or workflow fulfillment policy maps
/verification/status == "fulfilled"to task completion; - if
result_contract.pointer_fieldsincludesreviewed_path, the field must always be present, including rejection cases.
Sequence of Steps¶
Step 0: Operator launches the workflow from a template¶
The operator opens a thin client over node A and selects a workflow template from the catalog (proposal 029):
template_id: bielik-biweekly-publish.v1
parameters:
topic: "Bielik"
cadence_window: { from: "2026-04-03", to: "2026-04-17" }
repo: "git@bielik-blog:orbiplex/bielik-blog.git"
draft_branch_prefix: "drafts/bielik-"
publish_branch: "publish/main"
hugo_section: "posts"
Arca on node A builds a concrete WorkflowDefinition instance from
those parameters: five steps, each with a declared task_type target
(rather than a hard-coded participant). At runtime Arca asks the
offer-catalog surface who currently offers the corresponding
service/type. Thanks to this, if node B fails and its role is taken
over by another node offering image-generate / image-place, the
workflow needs no editing.
{
"workflow_id": "wf:bielik-biweekly:01JRZ…",
"steps": [
{
"id": "research-and-draft",
"target": { "resolve": "task_type", "task_type": "draft-author" },
"input": { "topic": "Bielik", "cadence_window": { … } },
"timing": { "timeout": "PT90M", "on_timeout": "fail" }
},
{
"id": "illustrate",
"target": { "resolve": "task_type", "task_type": "image-place" },
"input_from_step": "research-and-draft",
"timing": { "timeout": "PT30M", "on_timeout": "fail" }
},
{
"id": "editorial-review",
"target": { "resolve": "task_type", "task_type": "editorial-review" },
"input_from_step": "research-and-draft",
"depends_on": ["research-and-draft"],
"timing": { "timeout": "PT30M", "on_timeout": "fail" }
},
{
"id": "publish",
"target": { "resolve": "task_type", "task_type": "git-push-publish" },
"input_from_step": ["illustrate", "editorial-review"],
"depends_on": ["illustrate", "editorial-review"],
"timing": { "timeout": "PT60M", "on_timeout": "fail" },
"retry": { "max_attempts": 1, "backoff_seconds": 300 }
},
{
"id": "verify-publication",
"target": { "resolve": "task_type", "task_type": "publication-verifier" },
"input_from_step": "publish",
"depends_on": ["publish"],
"timing": { "timeout": "PT10M", "on_timeout": "fail" }
}
]
}
The five timing.timeout values are a concrete application of
temporal orchestration from proposal 033: if a provider does not
return a result within the time window, the step is marked
timed_out and the workflow halts with that status. There is no
silent rescue and no fallback to another node — the operator is
supposed to see that something is stuck.
Communication Workflow¶
The diagrams below are intentionally vertical transmission scenarios, not one large component graph. Each scenario shows the messages that cross a boundary: either inside one node or between two nodes. The article bytes are never passed through Arca or Agora; only pointers, order envelopes, facts, and audit identifiers move through the control plane.
Step 0 — local workflow instantiation on node A¶
sequenceDiagram
autonumber
participant Operator as Operator thin client
participant DaemonA as Node A daemon
participant StoreA as Host-owned module store
participant ArcaA as Arca on node A
participant Offers as Offer catalog view
Operator->>DaemonA: Select template bielik-biweekly-publish.v1 + parameters
DaemonA->>StoreA: Load workflow-template record
StoreA-->>DaemonA: Template payload
DaemonA->>ArcaA: Instantiate WorkflowDefinition
ArcaA->>Offers: Resolve task_type targets to active service offers
Offers-->>ArcaA: draft-author → node A, image-place → node B, editorial-review + git-push-publish → node C
ArcaA-->>DaemonA: Persist workflow run with resolved step plan
DaemonA-->>Operator: Workflow run accepted
Step 1 — node A researches, drafts, signs, and publishes a draft fact¶
sequenceDiagram
autonumber
participant ArcaA as Arca on node A
participant DatorA as Dator on node A
participant ResearcherA as bielik-researcher
participant SensoriumA as Sensorium core + OS connector
participant MemariumA as Memarium on node A
participant AD as Artifact Delivery
participant Git as Git repository
participant AgoraRelay as Editorial Agora relay
ArcaA->>DatorA: service-order research-and-draft
DatorA->>ResearcherA: Route accepted order to JSON-e role adapter
ResearcherA->>SensoriumA: Query admitted observations and invoke allowlisted fetches
SensoriumA-->>ResearcherA: Public-source facts and connector outcomes
ResearcherA->>MemariumA: Read previous articles and editorial idiolect facts
MemariumA-->>ResearcherA: Local memory context
ResearcherA->>SensoriumA: Invoke drafting script through allowlisted action
SensoriumA-->>ResearcherA: Markdown draft artifact
ResearcherA->>SensoriumA: Invoke worktree write, git commit, and draft-branch push
SensoriumA->>Git: Write content, commit with git.commit.v1 marker/signature, push drafts/*
Git-->>SensoriumA: draft_branch, draft_commit, draft_path
ResearcherA->>MemariumA: Append story009.git-commit-produced fact
ResearcherA->>AD: Send signed agora-record.v1 fact pointer
AD->>AgoraRelay: Publish through agora-default route
ResearcherA-->>DatorA: Step result with git pointers + memarium_record_id
DatorA-->>ArcaA: Complete research-and-draft
Step 2 — node B receives only pointers, pulls bytes through git, and returns illustration pointers¶
sequenceDiagram
autonumber
participant ArcaA as Arca on node A
participant DatorB as Dator on node B
participant IllustratorB as illustrator module
participant SensoriumB as Sensorium core + OS connector
participant MemariumB as Memarium on node B
participant AD as Artifact Delivery
participant Git as Git repository
participant AgoraRelay as Editorial Agora relay
ArcaA->>DatorB: service-order illustrate with draft_branch, draft_commit, draft_path
DatorB->>IllustratorB: Route accepted order to JSON-e role adapter
IllustratorB->>SensoriumB: Invoke git fetch + checkout for draft pointers
SensoriumB->>Git: Fetch drafts/* and checkout draft_commit
Git-->>SensoriumB: Draft markdown bytes in local worktree
IllustratorB->>MemariumB: Read aesthetic policy and prior review facts
MemariumB-->>IllustratorB: Local visual style context
IllustratorB->>SensoriumB: Invoke diffusion script and scoped worktree writes
SensoriumB-->>IllustratorB: Image artifacts and markdown patch outcome
IllustratorB->>SensoriumB: Invoke signed commit and draft-branch push
SensoriumB->>Git: Commit images + markdown references, push drafts/*
Git-->>SensoriumB: illustrated_commit
IllustratorB->>MemariumB: Append illustration decision fact
IllustratorB->>AD: Send signed agora-record.v1 fact pointer
AD->>AgoraRelay: Publish through agora-default route
IllustratorB-->>DatorB: Step result with illustrated_commit + memarium_record_id
DatorB-->>ArcaA: Complete illustrate
Steps 3 and 4 — node C reviews through git, then performs the only publish push¶
sequenceDiagram
autonumber
participant ArcaA as Arca on node A
participant DatorC as Dator on node C
participant EditorC as editor-in-chief
participant PublisherC as git-publisher
participant SensoriumC as Sensorium core + OS connector
participant MemariumC as Memarium on node C
participant Git as Git repository
participant Netlify as Netlify deploy hook
ArcaA->>DatorC: service-order editorial-review with draft pointers
DatorC->>EditorC: Route editorial-review order
EditorC->>SensoriumC: Invoke git fetch + checkout for draft_commit
SensoriumC->>Git: Fetch drafts/* and checkout draft_commit
Git-->>SensoriumC: Draft markdown bytes in local worktree
EditorC->>EditorC: Run guardrails-as-code
EditorC->>SensoriumC: Invoke proofreading script and scoped worktree writes
SensoriumC-->>EditorC: Reviewed markdown artifact and patch outcome
EditorC->>MemariumC: Append editorial-review fact
EditorC-->>DatorC: Step result with editorial decision + memarium_record_id
DatorC-->>ArcaA: Complete editorial-review
ArcaA->>DatorC: service-order publish with illustration + editorial pointers
DatorC->>PublisherC: Route git-push-publish order
PublisherC->>SensoriumC: Invoke signed commit, fast-forward merge, publish/main push
SensoriumC->>Git: Commit review, merge draft branch, push publish/main
Git-->>Netlify: publish/main update triggers deployment
Git-->>SensoriumC: publish_commit
PublisherC->>MemariumC: Append publication decision fact
PublisherC-->>DatorC: Step result with publish_commit + memarium_record_id
DatorC-->>ArcaA: Complete publish
Step 5 — verified workflow completion is announced as metadata, not content¶
sequenceDiagram
autonumber
participant ArcaA as Arca on node A
participant MemariumA as Memarium on node A
participant AD as Artifact Delivery
participant AgoraRelay as Editorial Agora relay
participant NodeB as Node B observers
participant NodeC as Node C observers
participant Operator as Operator thin client
ArcaA->>ArcaA: Mark workflow run completed
ArcaA->>MemariumA: Append workflow.completed local fact
ArcaA->>AD: Send signed workflow.completed agora-record.v1
AD->>AgoraRelay: Publish with Memarium and git pointers
AgoraRelay-->>NodeB: Observed completion metadata
AgoraRelay-->>NodeC: Observed completion metadata
NodeB->>NodeB: Append observation to local Memarium
NodeC->>NodeC: Append observation to local Memarium
ArcaA-->>Operator: Show completed run, durations, publish/main@commit
Step 1: Node A does research and writes a draft¶
Arca dispatches research-and-draft to the selected active offer for
the draft-author task type (node A in this story). The order arrives
at Dator on node A, which validates the order against node A's
acceptance posture (task type still offered, queue not saturated,
local role module ready), accepts it, and routes the payload to the
local role capability ready), accepts it, and routes the payload to the
bielik-researcher JSON-e role adapter. Dator then tracks order state and
surfaces the provider result back to Arca. The research adapter:
- Asks Sensorium about changes to the topic
Bielikin thecadence_window: new releases on HF, commits in thespeakleash/Bielik-*repo, new issues and discussions, mentions in selected feeds. Sensorium returns admitted facts about public sources. If a source requires an operational fetch rather than an already-admitted observation, the researcher askssensorium-coreto invoke an allowlisted OS connector action; the researcher does not run shell commands directly. -
Asks its own, local Memarium (node A's Memarium) about two things:
-
previous articles in this cycle (its own drafts plus publication records observed from node C — all earlier imported through explicit workflow/local-relay contracts),
- editorial-characteristic phrases and preferred stylistic constructions (the editorial idiolect — see seqnote — preserved in node A's Memarium as its view of the shared style, fed by corrections observed from node C).
- Asks
sensorium-coreto invoke an allowlisted OS connector action that runs a local drafting script (a thin wrapper over the node's research/drafting model). The script takes the admitted Sensorium facts and the Memarium context as typed parameters, runs under the connector's timeout/cwd/output-cap discipline, and returns a markdown draft in Hugo format as a captured artifact:
---
title: "What's new with Bielik — April, part two"
date: 2026-04-17T11:00:00+02:00
draft: true
tags: ["bielik", "llm", "polski"]
---
In the past two weeks, around Bielik, the following has happened…
After the draft is produced, the git write is handled as a separate
Arca task path (git-commit-draft). The git-signer role asks
sensorium-core to invoke allowlisted OS connector actions that clone
the repo (if needed), create the branch
drafts/bielik-2026-04-17-A, writes the file
content/pl/log/2026/bielik-co-nowego.md, commits with a git
signature tied to node A's participant key (Ed25519 over the
canonicalised commit object — the same signing mechanism used by the
Agora relay in story 008, only with a different domain tag:
git.commit.v1), and pushes the branch. The signature may be produced by the
configured script through the scoped signer grant declared for that action;
sensorium-core and the host signer authorize the grant, while the OS connector
only runs the configured process. The configured script constructs the
Git-specific signing payload and finalises the commit. The git bytes still move
through git; Sensorium only mediates the local operation boundary and records
directive outcomes.
The step's result returned to Arca:
{
"outcome": "ok",
"draft_branch": "drafts/bielik-2026-04-17-A",
"draft_commit": "8fa2…",
"draft_path": "content/pl/log/2026/bielik-co-nowego.md",
"signature_tracker": {
"domain": "git.commit.v1",
"status": "verified",
"signer": "participant:did:key:z…",
"signature_digest": "sha256:…"
},
"memarium_record_id": "sha256:…"
}
signature_tracker.status is intentionally a structured value. A real signer
success uses a signed/verified status with key and signature metadata; a
development run without signer transport may use marker-only; a signer policy
refusal must surface as a denial status with a stable signer error code (for
example domain_not_authorized), not as a generic result-schema failure.
memarium_record_id points to the fact record "A produced draft X
in response to brief Y at time T" — this is a fact written into
node A's Memarium (the step's author), not overwritten state. For
signed commits, that fact also records the commit SHA, signature domain
(git.commit.v1), signer identity, signature value or digest, and the
Sensorium directive/outcome identifiers that caused the local action. In
parallel, the role flow publishes a host-owned workflow.step.completed record
on the daemon that executed the step. Other nodes do not receive a hidden copy;
any cross-node projection must be explicit. Later steps append further facts on
their own side; nothing disappears and nothing is mutated.
The memory projection boundary is intentionally above Sensorium. The OS
connector only returns the script's JSON plus artifacts; it does not know that
draft_commit is a Git commit, and it does not write Memarium facts. The role
module (for example git-signer or git-publisher) is the semantic owner that
transforms the Sensorium directive result into a memarium.write fact. Dator may
declare that a service offer is expected to produce such a fact, but Dator should
not interpret commit signatures or construct the Memarium payload itself.
The role module should carry an idempotency key into memarium.write, derived
from the dispatch id, fact kind, correlation id, and Sensorium outcome id, so a
retry after a post-write failure returns the same fact instead of duplicating the
causal record.
A minimal fact payload for a signed commit can therefore be shaped as data derived from the script JSON plus the Sensorium envelope:
{
"fact/kind": "story009.git-commit-produced",
"fact/schema": "story009.git-commit-produced.v1",
"subject": {
"kind": "git-commit",
"id": "8fa2…"
},
"produced_by": {
"role": "git-signer",
"node": "node:did:key:z…",
"participant": "participant:did:key:z…"
},
"git": {
"branch": "drafts/bielik-2026-04-17-A",
"commit_sha": "8fa2…",
"paths": ["content/pl/log/2026/bielik-co-nowego.md"]
},
"signature": {
"domain": "git.commit.v1",
"status": "verified",
"signer": "participant:did:key:z…",
"signature_digest": "sha256:…"
},
"sensorium": {
"directive/id": "directive:…",
"outcome/id": "outcome:…",
"observation/ids": ["obs:…"]
}
}
In the current reference skeleton the commit carries only the trailer marker
Orbiplex-Signature-Domain: git.commit.v1; the same fact shape still applies,
but signature.status is marker-only until real Ed25519 commit-object signing
is wired through the scoped signing lane from proposal 048.
Step 2: Node B reads the draft and creates illustrations¶
Arca dispatches illustrate to the provider of image-place (node
B), passing the result of step 1 as input. The order is received by
Dator on node B, which accepts it under node B's acceptance
posture and routes it to the illustrator role module. The input is pointers
(draft_branch, draft_commit, draft_path, memarium_record_id),
not the draft bytes — the article content does not enter the Arca
data plane at all. The illustration module:
- Asks
sensorium-coreto invoke allowlisted OS connector actions forgit fetch origin drafts/bielik-2026-04-17-Aandgit checkout 8fa2…on a local worktree associated with the module; reads the filecontent/pl/log/2026/bielik-co-nowego.md. The draft bytes arrived through the git channel, not the Arca channel. - Extracts a list of visual motifs from the draft (title + selected headers + 1–3 longer paragraphs as prompt context).
- Asks its own, local Memarium (node B's Memarium) for the aesthetic policy — its own previous generative decisions plus acceptances and rejections observed from node C in earlier runs (palette, hero image format, what to avoid — e.g. "do not use generic stock images of server rooms").
- Asks
sensorium-coreto invoke an allowlisted OS connector action that runs a local illustration script wrapping the node's diffusion model. The script receives the visual motifs and aesthetic policy as typed parameters, generates a hero image + 2–4 in-text illustrations, and writes them tostatic/img/posts/2026-04-17/through a scoped worktree-write action (the same write-discipline used for markdown). The illustrator role module never loads the diffusion model in-process. - Edits the markdown file by asking the OS connector to apply the
allowlisted worktree write: adding
image:to the frontmatter and inserting{{< figure src="…" >}}at the appropriate places. - Through the
git-signerpath, asks the OS connector to commit on the same branch with a node B participant-key signature (git.commit.v1) and push the draft branch.
Result:
{
"outcome": "ok",
"illustrated_commit": "1d4c…",
"images_added": 4,
"memarium_record_id": "sha256:…"
}
Steps 3 and 4: Node C reviews, then publishes only if accepted¶
Arca dispatches editorial-review to node C after the draft step; it can
do this in parallel with illustrate, because both depend only on
research-and-draft. The input is pointer-only (draft_branch,
draft_commit, draft_path, memarium_record_id). The order is
received by Dator on node C, which routes it to the
editor-in-chief role module. The editorial module:
- Asks
sensorium-coreto invoke allowlisted OS connector actions forgit fetch origin drafts/bielik-2026-04-17-Aandgit checkout <draft_commit>— the draft bytes arrive through the git channel. - Reads the markdown draft. It does not wait for illustration artifacts; the publish step later joins the editorial and illustration branches.
-
Applies guardrails-as-code — rules baked into the module's code, not into a prompt:
-
editorial line check (forbidden phrases, source attribution requirement, title length limits);
- check that the frontmatter has the required fields (
title,date,tags); - check that
draft: truecan be removed; - link check (none returns 404).
- Asks
sensorium-coreto invoke an allowlisted OS connector action that runs a local proofreading script (a thin wrapper over the node's language model). The script reviews the material, performs minor punctuation, clarity and rhythm corrections when it accepts the piece, without changing the article's thesis, and returns a narrow JSON decision plus any revised markdown as captured artifacts. Theeditor-in-chiefrole module never loads the proofreading model in-process; guardrails-as-code from step 3 above remain in the role module (they are code, not a script). - Interprets the editorial decision as a workflow-local contract:
acceptedmeans the workflow may proceed to the separatepublishstep;rejectedmeans the role records the rejection fact, returns the rejection reason and correction pointers to Arca, and does not invoke the publish path. Arca may then pause, fail, or route a correction workflow according to theWorkflowDefinition.
Only after both illustrate and editorial-review complete does Arca
dispatch publish to the provider of git-push-publish (node C). The
git-publisher role asks the OS connector to perform the configured
allowlisted script: change the frontmatter (draft: false), commit if
needed, fast-forward/merge the accepted draft branch into publish/main,
and push publish/main to origin. Git semantics remain inside that
script and action declaration, not in Arca or Sensorium runtime.
The push to publish/main is the sole publication trigger: Netlify
listens to that branch and deploys. No other node has the key
authorised for that push — it is the only place where publishing
authority is centralised at the level of git operations, even though
the process is distributed. In implementation terms this is an explicit
Arca-mediated Sensorium OS directive path, not a Sensorium
research-observation connector path.
Result:
{
"outcome": "ok",
"editorial_decision": "accepted",
"reviewed_commit": "9a7e…",
"publish_branch": "publish/main",
"publish_commit": "9a7e…",
"memarium_record_id": "sha256:…"
}
For a rejected candidate the result is still pointer-only and auditable:
{
"outcome": "rejected",
"editorial_decision": "rejected",
"rejection_reason": "missing primary source attribution",
"correction_pointers": ["content/pl/log/2026/bielik-co-nowego.md"],
"memarium_record_id": "sha256:…"
}
Step 5: Verify publication fulfillment¶
Publishing and fulfillment are intentionally separate. git-push-publish
returning a commit id means that the publish step executed and produced a
pointer. It does not, by itself, prove that the publication task is fulfilled.
The workflow should therefore be able to declare an explicit fulfillment policy for the publication task. In the reference shape this is a follow-up decision source:
{
"step_id": "verify-publication",
"service_type": "publication-verifier",
"input_from": ["publish"],
"fulfillment": {
"policy": "external_decision",
"decision_source": {
"kind": "capability",
"capability_id": "story009.publication.verify"
},
"result_match": {
"path": "/verification/status",
"fulfilled_values": ["fulfilled"],
"not_fulfilled_values": ["not_fulfilled", "rejected"]
},
"on_not_fulfilled": "pause",
"on_error": "fail"
}
}
The verifier may use the Sensorium OS connector and an allowlisted script to
run git fetch, inspect refs, and decide whether the requested commit is
reachable from the publication ref. That is only one implementation. The
fulfillment decision could also come from another Sensorium connector, a
different middleware capability, or an operator/requester confirmation. The
workflow must name that source explicitly.
This keeps the domain-specific knowledge above Arca, Dator, Memarium, and Sensorium. The Git-aware script or role module knows what "published" means; Arca only records the declared decision, persists its evidence, and decides whether the workflow may continue or complete.
Example verifier output:
{
"schema": "task-verification-result.v1",
"verification/status": "fulfilled",
"verification/kind": "story009.publication-visible",
"verified_at": "2026-04-19T12:00:00Z",
"evidence": {
"kind": "git-ref-contains-commit",
"commit_sha": "9a7e...",
"ref": "origin/publish/main",
"reachable_from_ref": true
},
"retryable": false,
"memarium_record_id": "sha256:..."
}
The task-verification-result.v1 shape is a story-local convention until a
second workflow needs the same contract. It should not be promoted into a global
schema prematurely.
Finalization: Arca closes the workflow and announces the publication fact¶
Arca marks the workflow run as completed, records the full audit
trail (input, output and time of each step, all participant-key
signatures) and emits an agora-record.v1 record with
record/kind: "workflow.completed" for monitoring views and history.
The operator sees in the thin client:
"Workflow
bielik-biweekly-publish.v1finished. Step 1: node A (53 min). Step 2: node B (12 min). Step 3: node C (8 min). Verification: fulfilled. Publication:publish/main@9a7e…. Netlify deploy in progress."
A few minutes later the article is live. No human clicked a "publish" button in a CMS panel.
Two completion planes¶
Story-009 deliberately separates two completion planes:
workflow.step.completedis a host-owned audit/read-model record accepted by the daemon that executed the role step. In a three-node run, node A, node B, and node C each expose their local records through/v1/workflows/runs/{workflow_run_id}/steps/completed. This plane is not an Agora topic and does not create a hidden shared store.workflow.completedis the final workflow-level monitoring fact emitted by Arca after all required step outputs have been collected. It may be published to local Agora asrecord/kind: "workflow.completed"for UI, operator, and history views.
Reconstruction of the editorial path reads the per-node step-completion read
models and follows their explicit memarium_record_id pointers. Public or
team-facing monitoring reads the final workflow.completed record.
A production operator view should aggregate these read models as a read-side
projection, not as a new write plane. The aggregator may query each configured
node's control endpoint, merge records by workflow/run-id and
workflow/phase-id, and show missing, duplicate, or unexpected step ids. It
must not publish synthetic workflow.step.completed records to Agora and must
not treat the final workflow.completed monitoring fact as the source of truth
for per-step completion. The current operator helper
node/tools/acceptance/story-009-step-completions.py --expect-complete is the
CLI shape of that projection.
Acceptance Criteria¶
| # | Criterion | Verification |
|---|---|---|
| 1 | The WorkflowDefinition has five steps with resolve: task_type targets; it contains no hard-coded participant identifiers |
inspection of the saved workflow run |
| 2 | Step 1 (research-and-draft) ends with a commit on the drafts/bielik-…-A branch signed by node A's participant key with signature domain git.commit.v1 |
verification of the commit object signature |
| 3 | Step 2 (illustrate) commits on the same branch, adding ≥1 new image in static/img/posts/<date>/ and at least one {{< figure >}} reference in the markdown file |
content diff between draft_commit and illustrated_commit |
| 4 | Step 3 (editorial-review) either rejects the candidate and emits a rejection fact without pushing, or accepts the candidate and returns a pointer-only review fact; step 4 (publish) then changes draft: true to draft: false, fast-forwards publish/main to the accepted draft and pushes only that branch |
inspection of the origin repo's git reflog plus the editor-in-chief Memarium fact |
| 5 | Only node C offers the git-push-publish task type and only node C's Sensorium OS connector has an allowlisted publish action for publish/main; an attempt to push publish/main from node A or B fails at git policy level (origin-side or pre-receive hook) or at Sensorium directive admission |
negative test: a manual git-push-publish invocation from node A must be rejected before or at git push |
| 6 | Each of the five steps, if it exceeds timing.timeout, ends the workflow run with status timed_out indicating the specific step; there is no silent fallback to another provider |
test: an artificial sleep in one of the modules longer than the timeout |
| 7 | Each step appends at least one record to the local Memarium of the node executing the step (not to any shared store), whose identifier it returns in the step output; records are appended (append-only), not overwritten | inspection of each of the three Memariums between run 1 and run 2 — all records from run 1 retained |
| 8 | After completed, Arca emits an agora-record.v1 record with record/kind: "workflow.completed" containing record/about with links to the three commit-producing Memarium records plus the publication verification Memarium record |
inspection of the Agora relay after the finished run |
| 9 | The cycle can be reproduced from the workflow run + the sum of the three local Memariums — one can reconstruct who proposed what, what was rejected at review and why, without referring to any shared store | test: delete the working tree, reconstruct the publication path solely from the records of the three Memariums + the Arca log |
| 10 | Netlify deploys only as a result of the step 4 push; a manual change to publish/main outside an Arca run is technically possible, but becomes a record visible in the log (not a hidden path) |
inspection of the Netlify deployment history vs. the Arca log |
| 11 | No single shared Memarium: each of the three nodes has its own instance; step-completion reconstruction uses per-node host-owned workflow read models with explicit Memarium pointers, while the final workflow.completed monitoring fact may be published to local Agora |
configuration inspection: each node has its own memarium-store; each executing node exposes /v1/workflows/runs/{workflow_run_id}/steps/completed; no shared mountpoint, no data branch that everyone sees without going through an explicit workflow or relay contract |
| 12 | The article content (markdown + images) does not at any point enter the Arca data plane nor the content of Agora records. Between steps only pointers (branch, commit, paths, memarium_record_id) are passed; draft and image bytes flow exclusively through git |
inspection of input / output of each step in the workflow run: payload size < 4 KiB, no fields with markdown or image bytes; inspection of Agora records — content contains only metadata, not the article corpus |
| 12a | Any Agora record in this story that carries Memarium-derived pointers or summaries is classified for public egress: classification.effective_tier = "Public" and bound_subjects.public_projection is present; full subject refs are not published |
Agora ingest/relay inspection plus classification egress guard denial tests |
| 13 | Every module executing a git-using step starts by invoking the Sensorium OS connector for git fetch/git checkout based on the pointer from the previous step; there is no direct shell path and no alternative path through which draft bytes could reach the module |
code review of the illustrator and editor-in-chief modules plus Sensorium directive/outcome audit: the only source of draft content is the local git worktree |
| 14 | Every service order dispatched by Arca is received by the local Dator on the responder node and routed to the corresponding role capability provider; role adapters do not receive Arca orders on any other path. git-push-publish appears as an active offer only on node C's Dator |
offer-catalog inspection + Dator order log on each node |
| 15 | Every published offer in this story has price.amount = 0 in ORC |
offer-catalog inspection |
| 16 | All generative work (draft composition, image generation, language proofreading) is executed as a sensorium.directive.invoke call to an allowlisted OS connector action wrapping the corresponding local model/script; no role adapter loads a language or diffusion model in its own process space |
Sensorium directive/outcome audit + module/config review (no in-process model load) |
| 17 | The OS connector action catalog used in this story is declared in the connector's configuration file and authorized through the sidecar-signature mechanism from proposal 048 (node-signed on fresh install, operator-signed after any operator edit). Each action used by this story is declared under one of the classes from 048, and commit-signing actions declare a bounded signing.allowed_domains = ["git.commit.v1"] lane instead of receiving general signer access. The story introduces no parallel trust surface, no ad-hoc allowlist, and no separate signing artifact for OS actions. |
inspection of the OS connector configuration + sidecar signature file; class tags, argv shapes, result contracts, and signing domains match the Realisation table |
Acceptance Coverage Status¶
As of the reference implementation pass, story-009 has several distinct coverage levels. They should not be conflated.
| Coverage level | Executable coverage | Criteria covered | Remaining gap |
|---|---|---|---|
| In-process reference skeleton | node/tools/acceptance/story-009-reference-skeleton.py |
Exercises the intended strata Dator -> role adapter -> sensorium-core -> sensorium-os; validates production v1 Sensorium schemas; checks five workflow steps, zero-price offers, pointer-only step outputs, Git as the content data plane, guarded local publish/main, rejection path, publication verification, Memarium fact pointers, and action catalog pointer contracts. |
Uses in-process host-capability patching and signature_tracker.status = marker-only; does not prove daemon supervisor, module authtok, real signer lane, or cross-node separation. |
| Single-daemon supervised integration | cargo test -p orbiplex-node-daemon --test story_009_sensorium_role_dispatch |
Runs the same seam through real supervised processes plus in-process json_e_flow role providers, module authtoks, host capability registry, host-owned module store replay, Arca workflow instantiation, Dator offer lookup, service orders, real daemon-scoped git.commit.v1 signer lane, Memarium commit facts, publication verification fact, and a local Agora workflow.completed record linking the three commit facts plus verification. Covers the reference form of criteria 1, 2, 3, 4, 8, 12, 12a, 13, 15, 16, and 17. |
Still a single-daemon deployment. It proves the contract seam, not the physical invariant that node A, node B, and node C are separate hosts with separate Memariums and only node C's connector able to push. |
| Guarded local Git origin | Both reference skeleton and daemon integration | Proves the publish authority shape for criteria 4, 5, and 10 with a local bare origin and pre-receive guard. |
Does not exercise Netlify or an external Git host. Netlify remains deployment infrastructure outside the Orbiplex control plane. |
| Three-daemon topology smoke | story_009_three_node_topology_harness_starts_isolated_daemons in node/daemon/tests/story_009_sensorium_role_dispatch.rs |
Starts three isolated daemon data dirs; verifies distinct control tokens and middleware registries; checks node-specific Dator service-type reports; checks node-specific Sensorium OS action catalogs; proves node C is the only daemon carrying story009.review.publish and the guarded publish token. Covers the topology slice of criteria 5, 11, and 14. |
Does not perform editorial execution; it only proves deployment separation and node-specific authority shape. |
| Three-daemon execution and reconstruction | story_009_three_node_execution_reconstructs_workflow_completion_from_local_memaria in node/daemon/tests/story_009_sensorium_role_dispatch.rs |
Executes the story steps across three isolated daemon nodes: node A drafts, node B illustrates, node C reviews/publishes/verifies. Each node writes to its own local Memarium; the harness reconstructs a workflow.completed-style record with explicit links to the three commit-producing Memarium facts plus the publication verification fact. Covers the executable reconstruction slice of criteria 5, 7, 9, 11, and 14. |
Dispatch is still test-orchestrated by direct calls to each node-local Dator endpoint. It does not yet prove Arca-driven peer service-order routing or signed local Agora fact exchange between nodes. |
| Arca-driven remote-provider execution over WSS peer catalog and peer sessions | story_009_arca_drives_remote_providers_and_reads_host_owned_step_completion in node/daemon/tests/story_009_sensorium_role_dispatch.rs |
Runs Arca on node A as the orchestrator. Step-completion reconstruction is host-owned rather than Agora-owned. Node B/C publish offer-catalog capability passports to their embedded Seed Directory surfaces; node A is configured with Seed Directory endpoints and an explicit catalog_peer_discovery_policy allowlisting the expected provider node ids, but it does not seed their offers and does not use catalog_peer_node_ids. Arca discovers catalog peers through seed.directory.query with a Seed Directory predicate over trusted node ids, fetches their Dator catalogs through offer-catalog.fetch.request/response over established daemon-owned WSS peer sessions, persists the discovered offers in its observed catalog, selects node B/C providers, dispatches remote-provider service orders through daemon-owned peer.message.dispatch, receives service-order.dispatch.response over the same peer-session correlation path, completes the workflow from pointer-only step outputs, publishes the final workflow.completed monitoring record, and verifies the editorial path by reading the per-node host-owned /v1/workflows/runs/{workflow_run_id}/steps/completed read models. The step-completion records validate against story009.workflow-step-completed.v1, carry non-null workflow/run-id and workflow/phase-id for every step, and the test asserts that node A/B reject git-push-publish while their Sensorium OS catalogs do not expose the publish action. Covers the current executable form of criteria 5, 7, 8, 9, 11 and 14. |
The allowlisted trusted_node_ids policy remains a harness/deployment override. Production profiles should replace or enrich it with broader trusted-peer policy, capability-passport verification, Seed Directory assurance, and federation endorsement policy. |
| Peer service-order dispatch contract | python3 -m unittest middleware-modules/arca/test_service.py middleware-modules/dator/test_service.py plus daemon peer response-kind coverage |
Freezes the transport-shaped seam: Dator handles service-order.dispatch.request on inbound-peer with an explicitly declared long-running chain timeout and executes the same local role capability path; Arca derives a peer-message remote target from an observed offer, sends it through daemon-owned peer.message.dispatch, and correlates service-order.dispatch.response by request_id. |
Unit-level coverage plus spawned-daemon WSS coverage. daemon_peer_listener_accepts_dialer_and_dispatches_peer_message proves generic WSS peer-message delivery; the story-009 Arca-driven harness proves the full service-order dispatch path over established WSS peer sessions. |
Story-009 is a closed operator-owned editorial deployment, not a public
capability federation. Its offer-catalog passports are deployment catalog
facts used to let node A discover the known provider nodes B/C. They do not
claim that B/C are community-wide official offer-catalog providers. A public
network deployment should layer federation endorsement, higher-assurance issuer
policy, or identity-anchored custom capability names on top of this harness
shape.
When a Story-009-specific capability is published outside this closed
deployment shape, it should use the sovereign custom convention from Proposals
024/025: ~story009-...@participant:did:key:... or
~story009-...@org:did:key:... with its own schema/ref. A sovereign id
without ~ is reserved for compatibility claims and should carry
capability_profile.compatible_with.
What This Story Does NOT Cover¶
- Memarium federation beyond the editorial team. Each of the three
nodes has its own local Memarium; editorial consistency for this harness is
reconstructed from explicit Memarium pointers in the per-node host-owned workflow
step-completion read models, with only the final monitoring fact published as
workflow.completedon local Agora. Synchronisation with other editorial teams or with public Agora records is a separate story. - Artefact exchange other than via git. The INAC channel
(proposal 042) and the
memarium-blob.v1schema are the natural extension direction (large binary assets unfit for git, confidential briefs), but this story deliberately does not use them — all bytes of "the work" flow exclusively through the git repository, so as not to introduce a second artefact transport alongside the one already required. - External rate negotiation. This story assumes suitable active
offers already exist for the needed
service/typevalues. It does not cover negotiation of new rates or dynamic commercial terms with external providers. - Fan-out to many researchers or many illustrators. This workflow is sequential 1→1→1. The variant "three illustrators compete, review picks the best" is a direct application of the fan-out from proposal 033 and is a separate story.
- Module reputation and automatic calibration. Here the operator decides that node A "is good at research". Reputation mechanisms are out of scope.
- Proxy / delegated keys. All signatures use
KeyRef::PrimaryParticipant. The variant withkey-delegation.v1(proposal 032) would be a separate flow. - Full anti-hallucination policy. The guardrails-as-code on node C are illustrative here (a few simple rules). The full catalogue of editorial rules is a separate artefact.
Architectural Significance¶
This story is the smallest coherent slice that simultaneously shows five Orbiplex commitments expressed in the seqnote:
- The editorial team as a swarm, not a pipeline. The three
nodes are not stages of a script but autonomous participants
with active offers for declared task types. Arca only proposes
cooperation — the offer catalog resolves providers at run time.
Replacing node B with any other node that offers
image-generatedoes not require workflow edits. - Memory as infrastructure, not a log; local, not shared. Each node keeps its own Memarium as an appended stream of facts from which the path of its own decisions can be reconstructed. "Editorial memory" as a whole is an emergent view of the three Memariums tied together by explicit workflow step-completion records and final monitoring facts. No one owns shared state; everyone owns their own history and their observation of others' facts. Nothing is mutated "in place".
- Transport stratification: data plane = git, control plane =
Arca + Agora. The bytes of "the work" (markdown, images,
corrections) flow exclusively through the git repository, which is
already required by the publishing architecture. Between steps
only pointers (
branch,commit,memarium_record_id) are passed — fitting like a glove into Arca'sinput_from_stepand into thecontentofagora-record.v1records. We do not introduce a second artefact transport alongside the existing one; this brings a concrete engineering benefit (zero new transport code, natural auditability viagit log) and a conceptual one (each layer does what it is suited for). - Publication authority is explicit and local. Even though the process is distributed, the push authority to the branch tracked by Netlify is held by exactly one task type provider on exactly one node. Decentralising intelligence does not mean decentralising every single operation — sensitive operations remain explicitly concentrated, and the rest of the process works around them.
- Boundaries are contractual, not ad-hoc. Each step has a
WorkflowDefinitionwith explicit input, output, deadline and retry policy. Nobody "understands without words" — every state transition is contractually described and auditable.
The value of this story lies, similarly to story 008, in negative simplicity: the publishing infrastructure itself (Hugo + git + Netlify) is ordinary, drily ordinary. The novelty is that the editorial process over that infrastructure is described as the cooperation of signed nodes rather than as one monolithic CI script or one freewheeling AI worker.
Implementation and Hardening Map¶
The table below is a map, not a mirror. It distinguishes solution capability
sidecars (e.g. arca-caps.edn) from marketplace task_type / service/type
names advertised through offers. The reference implementation column describes
what the current node workspace already exercises. The hardening column names
what remains before treating the same seam as a production deployment.
| Scope | Component | Reference implementation status | Production hardening remaining | Solution document |
|---|---|---|---|---|
| Step 0 (local template persistence substrate) | Orbiplex Node (daemon) | Done as substrate: module-scoped JSON records can persist record_kind = workflow-template locally without daemon schema changes. |
Public/federated template lifecycle remains optional for this story. | host-owned-module-store.md, 044-host-owned-generic-module-store.md |
| Step 0 (instantiating workflow from template) | Arca | Done for local templates: template_id + parameters resolve into a concrete WorkflowDefinition and run. |
Public/federated template import should go through a template catalog if the story is distributed as a reusable package. | arca.md |
| Step 0 (optional public template catalog) | Dator / template catalog module | Not needed by the current daemon-local profile. | Implement publication, listing, fetch, and import handoff only when bielik-biweekly-publish.v1 becomes a public/federated template. |
029-workflow-template-catalog.md |
| Step 0 (resolving targets by task type) | Arca + offer catalog | Done for the current rule: target.resolve = task_type maps target.task_type to provider service/type without host fan-out unless fan_in is present. |
Production discovery policy should move beyond harness allowlists into trusted peer, passport, endorsement, and Seed Directory predicate policy. | arca.md |
| Steps 1–3 (offer publication + order acceptance, per node) | Orbiplex Dator on nodes A, B, C | Done in the reference harness: Dator publishes zero-price offers, accepts Arca-dispatched service orders, routes them to role capability providers, and exposes git-push-publish only on node C. |
Harden queue/concurrency posture, refusal diagnostics, production pricing if non-zero work is introduced, and federation-grade discovery/endorsement policy. | planned Dator solution doc |
| Step 1 (research + draft) | bielik-researcher role provider on node A + Sensorium OS connector |
Done as a json_e_flow role provider invoking the allowlisted story009.draft.compose OS action; the action returns pointer-only git/Memarium output. |
Replace deterministic fixture logic with real public-source/LLM wrappers as operator-controlled scripts; keep Git and LLM semantics inside scripts, not Arca, Dator, Sensorium-core, or the connector implementation. | planned bielik-researcher solution doc |
| Step 1 (signed commit) | role provider + Sensorium OS connector + daemon signer lane | Done in supervised integration: commit tracking uses the daemon-scoped git.commit.v1 signer lane and records signature_tracker.status = signed. |
Non-demo deployments should use operator-reviewed action catalogs and scoped signer grants; delegated/proxy-key variants remain a separate flow. | planned git-signer solution doc |
| Step 2 (illustrations) | illustrator role provider on node B + Sensorium OS connector |
Done as a json_e_flow role provider invoking the allowlisted story009.image.place OS action; output remains pointer-only. |
Replace fixture image placement with real diffusion/asset tooling through scripts and declared artifacts; keep payload bytes out of Arca and Agora. | planned illustrator solution doc |
| Step 3 (review + guardrails-as-code) | editor-in-chief role provider on node C + Sensorium OS connector |
Done as story009.editorial.review, including acceptance/rejection behavior and local Memarium fact output. |
Expand guardrails-as-code from reference checks into an operator-owned editorial rule catalog; keep rejection reasons structured. | planned editor-in-chief solution doc |
| Step 4 (push to publishing branch) | git-publisher role provider on node C + Sensorium OS connector |
Done against a guarded local bare origin through story009.review.publish; node A/B negative authority is tested. |
Connect to a real Git host/Netlify deployment target and require operator-signed authorization for C7 publishing actions in non-demo profiles. | planned git-publisher solution doc |
| Step 5 (publication fulfillment verification) | publication-verifier role provider on node C + Sensorium OS connector |
Done through story009.publication.verify; Arca applies the workflow's output_match fulfillment policy to the narrow JSON result. |
For external deployments, verify the proof against remote origin/deployment state rather than only the local guarded origin. | planned publication-verifier solution doc |
| Step completion audit | Daemon host-owned workflow read model | Done: role providers publish workflow.step.completed through the daemon, and each executing node exposes /v1/workflows/runs/{workflow_run_id}/steps/completed. |
Add an operator UI/read-side aggregator over the three control endpoints; do not move per-step completion into Agora. | host-owned-module-store.md |
| Step-local memory | Memarium | Done in the reference harness: every executing node writes local Memarium facts and returns stable memarium_record_id pointers. |
Federation beyond the editorial team and cross-team synchronization remain separate stories. | memarium.md |
| Workflow-level monitoring | Arca + Orbiplex Agora | Done: Arca emits a local agora-record.v1 with record/kind = workflow.completed linking run id, step outputs, Memarium ids, git refs, and publication verification evidence without embedding content bytes. |
Production views should treat this as monitoring/history, not the source of truth for per-step completion. | agora.md |
Task-type anchors (marketplace/service names, not protocol capability passports):
:workflow-template-instantiateinarca-caps.edn— acceptstemplate_id+ parameters, returns a concreteWorkflowDefinitioninstance ready to dispatch.llm-researchanddraft-authoras two separate task types — research can in the future be split off from writing (e.g. one node gathers material, another writes), without changing the workflow.guardrails-as-codeas a task type separate fromeditorial-review— signals that the editorial line rules are baked into the module's code, not into the model's prompt.git-push-publishas a task type separate fromgit-commit-draftandgit-commit-illustrated— the fact that only one node offers it is the proper mechanism to enforce a single point of publishing authorisation.
Implementation anchors (status in
node/docs/implementation-ledger.toml and the modules' backlogs; not
here):
node/agora-core— used to sign theworkflow.completedrecord.- Role adapter middleware instances (
bielik-researcher,illustrator,editor-in-chief,git-signer,git-publisher) run asjson_e_flowconfiguration under proposal 049. They are first-class middleware providers but not supervised HTTP daemons in the official profile. Orbiplex Dator— runs on each of the three nodes as the supply-side marketplace facade that publishes the node's activetask_typeoffers and accepts Arca-dispatched service orders on behalf of the local role capability providers.- Sensorium OS connector — used by those role adapters for allowlisted
local OS actions through
sensorium.directive.invoke; role adapters do not callsensorium.connector.invokedirectly. Action classification and catalog authorization follow proposal 048 (classes C1..C7, sidecar signature, node-signed factory bootstrap); the five story-009 scripts ship as factory defaults and are node-signed on first start, so the demo runs without any operator signing ceremony. - Arca workflow — proposal 029 (templates) + proposal 033 (temporal orchestration) as the contractual base.
Production Readiness Checklist¶
Before treating story-009 as a production deployment, verify these decisions in configuration, UI, and tests:
- GitLab remote is configured as
git@bielik-blog:orbiplex/bielik-blog.giton every node that needs Git access, with private keys installed only as deployment secrets. - Hugo paths are created under
content/pl/log/<year>/andcontent/en/log/<year>/before the first real article run. - Only node C can push
publish/main; node A and node B must fail before or at push if they attemptgit-push-publish. git-push-publishrequires an operator-signed approval artifact and surfaces anAwaiting acceptanceUI item with[Sign]and[Reject].- Capability discovery uses passport and issuer policy. Harness
trusted_node_idsare not the only production trust gate. - Arca supports automatic provider selection plus operator override for the run.
- The step-completion UI is a read-side aggregator over per-node control endpoints, not an Agora topic and not a shared store.
workflow.completedis emitted as a monitoring/history fact after completion.- The verifier checks the deployed URL under
https://bielik.orbiplex.ai/once Netlify is connected. - The audit bundle can be exported from Memarium facts, workflow step-completion records, signer traces, Sensorium outcomes, and the final monitoring fact.
- Retention is explicit: Memarium facts indefinitely, Sensorium artifacts by TTL, stdout/stderr artifacts one day by default.
- Story role providers are
json_e_flow; HTTP-local role modules remain legacy harness material.
Minimum negative tests for the production MVP:
- provider unavailable;
- step timeout;
- node C operator rejects publish;
- Git push rejected;
- signer locked or unauthorized;
- missing Memarium passport;
- Seed Directory returns only untrusted providers.
References¶
doc/project/40-proposals/029-workflow-template-catalog.md(WorkflowDefinition, template catalog)doc/project/40-proposals/033-workflow-fan-out-and-temporal-orchestration.md(timeout / retry / deadline; fan-out out of scope)doc/project/40-proposals/019-supervised-local-http-json-middleware-executor.md(modules as supervised processes behind host-owned contracts)doc/project/40-proposals/048-sensorium-os-connector-action-classes.md(action classes C1..C7, operator-editable catalog, sidecar authorization, node-signed factory bootstrap)doc/schemas/sensorium-os-error-codes.v1.schema.json(shared diagnostic code vocabulary for the reference Sensorium OS connector)doc/project/30-stories/story-000-two-nodes-see-each-other.md(node and participant identity)doc/project/30-stories/story-008-cool-site-comment.md(the pattern of a signed record as an auditable unit)doc/project/60-solutions/008-agora/008-agora-caps.edn(Agora catalog)doc/project/60-solutions/CAPABILITY-MATRIX.md(generated status view)- "The magazine publishes itself" (seqnote — the human motivation that this story renders into a minimal technical flow)