Sections
On this page
harness-blueprint
Use when a project needs a new multi-agent harness — a council, review board or debate — and no existing one fits. Triggers on "build a council", "set up a review board", "we need agents to deliberate on X", "собери совет", "нужен консилиум
Blueprint a Harness
Take a request like "we need a council to decide X" all the way to a validated, published harness version tailored to this project. Prerequisite: read flownix-basics for the hierarchy, statuses, the project config file and the harness subsystem.
Use / don't use
Use it when a project needs a new council / review board / debate and none of the existing harnesses fit, or when an existing one needs a materially different graph, role set or policy set — that is a new version through this same procedure, not an edit of a published one.
Not this skill: you hold a role in a running harness → flownix-harness-participant; you
hold judge/moderator → flownix-harness-moderator; you just want to look at one →
harness.get / harness.export.
0. Load project context
cat .flownix 2>/dev/null || cat .ai-flowParse the YAML for project_id and org_id. If neither file exists, run flownix-init first.
workspace_idin everyharness.*call is theproject_idfrom the project config. harness-service storesworkspace_idas a plain UUID with no foreign key and no validation: pass the wrong one and nothing errors — the harness is simply created in a scope nobody looks at. There is no second source for this value; never invent one.
Then get_project_policy({ project_id }). In spec_mode: "strict" any decision this council
produces will need a spec delta before the work can be closed — say so in the roles' instructions
so the council's output is written to be applicable, not just persuasive.
1. Interview
Ask all questions in one batch, each with its default shown, and let the user answer only what they care about. Never ask about the stack, architecture, conventions or spec mode — those are derived in step 2. Eight questions, no more.
| # | Question | Default if unanswered | Maps to |
|---|---|---|---|
| 1 | What is this council for, and what decision must it hand back? | (no default — must be answered; without it there is nothing to build) | harness.create name + description; picks the graph preset |
| 2 | Type: review / debate / design / audit? | inferred from Q1, else council | harness.create type (single_agent, pipeline, council, review_board, debate, custom); picks the graph preset |
| 3 | What goes in — a Flownix node, a diff, the spec, an external document? | the node the request came from; nothing else | version input_schema; source_node_ids on harness.start_run |
| 4 | Who takes part, and what is each participant's specialization? | preset's default roles (see step 4) | one harness.add_role per participant: key, name, objective, system_instructions, min_instances/max_instances |
| 5 | How is the winner picked — vote or judge? | judge for review/audit, vote for debate/design | a vote node (+ voting policy) or a judge node |
| 6 | Is a human gate needed, and at which step? | no gate | human_gate node + approval policy with effect require_approval |
| 7 | Any tool or data restrictions? | read-only: no file writes, no shell | tools / data_access policies via harness.set_policy |
| 8 | Budget — how many rounds, what cost ceiling? | 1 round, no explicit ceiling | budget policy; a loop node when rounds > 1 |
Rules for the interview:
- Defaults are applied silently, and reported back. After the batch, restate the resolved configuration (all eight lines, defaults marked as such) and get one confirmation before building. A user who answers nothing still gets a working harness.
- Q1 is the only blocking question. Everything else falls back.
- Anything derivable is not asked. If you are about to ask what language the project is in, you skipped step 2.
- The answers, not the preset, win. The preset from Q1/Q2 is a starting layout; Q4–Q8 reshape its nodes, roles and policies.
2. Derive the stack & architecture profile
A council that proposes a Kafka bus to a project that has none is worse than no council. Build
the profile once, before any role exists, and paste the identical block into every role's
system_instructions.
Extraction order — stop as soon as a line is answered, and prefer the cheap source:
get_project_policy({ project_id })— spec mode. Instrict, every role's instructions must also say: the decision has to be expressible as a spec delta on an existing doc node.list_docs({ project_id })plusrag_context/rag_queryon"architecture","stack","conventions". Ifbootstrap-spechas been run on this project, the profile comes almost entirely from these doc nodes and the repo is only spot-checked.- The repository, which wins on any disagreement with the docs — docs go stale, manifests
do not:
- manifests:
go.work, eachgo.mod, eachpackage.json; - service layout:
services/**, top-level apps (web,admin,landing, …); - contracts:
proto/**plusbuf.yaml/buf.gen.yaml; - storage and schema history:
migrations/**; - deploy:
k3s/**,Dockerfile*,docker-compose.yml, CI workflows; - build and test commands: the root
Makefile.
- manifests:
When the repo and the docs disagree, record the repo's answer in the profile and flag the stale doc to the user — it is a finding, not noise to swallow.
The block
Fill this template and reuse it verbatim across all roles of the harness. Same text everywhere: two roles working from different pictures of the same system is exactly the failure this section exists to prevent.
## Project stack & architecture (authoritative)
- Languages & runtimes: <…>
- Services & boundaries: <…>
- Transport & contracts: <…>
- Storage & migrations: <…>
- Build, test, deploy: <…>
- Conventions that constrain solutions: <…>
Propose solutions that fit this stack. A proposal that requires a new language,
framework, datastore, or transport must be marked as an explicit alternative and
carry a justification plus a migration cost estimate.Rules for filling it:
- Every line is backed by a file or a node. A category you could not confirm is written as
unknown — not found in <where you looked>, never guessed and never quietly dropped. - The closing paragraph is a constraint on the role, not advice. Keep it verbatim; it is what makes an off-stack proposal a labelled alternative instead of the default answer.
- Keep it to structure and contracts. No source code, no file-by-file inventory — the roles get a map, not the territory.
Worked example — this repository, filled from go.work, services/**, proto/** + buf.*,
migrations/**, k3s/** and the root Makefile:
## Project stack & architecture (authoritative)
- Languages & runtimes: Go (go.work workspace, one module per service); TypeScript/Next.js
for the three frontends.
- Services & boundaries: seven Go services under services/** — core-service (nodes, docs,
spec mode), auth-service, gateway (HTTP edge), mcp-server, harness-service, rag-service,
notification-service. Frontends: web, admin, landing.
- Transport & contracts: gRPC between services, proto/** generated with buf; HTTP/SSE at the
gateway and the MCP server.
- Storage & migrations: PostgreSQL with per-service SQL in migrations/**; Qdrant for RAG.
- Build, test, deploy: root Makefile targets, Go tests per module, k3s/** manifests, CI images.
- Conventions that constrain solutions: contract changes start in proto/** and are regenerated
with buf, never hand-edited; schema changes ship as a new migration, never as an edit to an
applied one; spec_mode=strict means closing a task needs an applied doc delta or an explicit
declare_no_spec_impact.3. Build
Fixed order — each call needs an ID the previous one returned:
// 1. The definition. workspace_id === project_id from the project config.
harness.create({ workspace_id, name, description, type }) // → harness_id
// 2. A draft version. Everything below hangs off this version.
harness.create_version({ harness_id, changelog: "initial blueprint",
input_schema }) // → harness_version_id
// 3. One call per participant from Q4. Paste the step-2 profile block into
// system_instructions, identically for every role.
harness.add_role({ harness_version_id, key: "architect", name: "Architect",
objective: "…", system_instructions: "…profile block…",
responsibilities: [...], prohibited_actions: [...],
min_instances: 1, max_instances: 3 }) // → role_id
// 4. Nodes, then edges.
harness.add_workflow_node({ harness_version_id, type: "agent_task",
name: "proposals", config: { role_key: "architect" } })
harness.connect_workflow_nodes({ harness_version_id, from_node_id, to_node_id })
// 5. Policies from Q6–Q8.
harness.set_policy({ harness_version_id, category: "budget", effect: "allow", … })Things that bite:
- Nodes reference roles by
role_keyinsideconfig, not byrole_id.configis free-form JSON with no schema validation — a typo'd key is not rejected here, it surfaces as a validation error in step 5 (or, worse, at run time). - Node types are exactly:
agent_task,parallel_group,review,debate,vote,judge,human_gate,transform,create_flow_nodes,condition,loop,end. - Policy categories:
tools,data_access,voting,budget,execution,approval,retention,privacy. Effects:allow,deny,require_approval.denybeatsallow— a broad allow plus a narrow deny is the way to express "everything except this". - Role instances:
max_instances > 1is what makes aparallel_groupactually parallel; leaving both at 1 yields a group of one. - Every graph ends at an
endnode. A branch that just stops is a validation error, not a graceful finish.
4. Graph presets
Pick by the answers to Q1/Q2, then reshape with Q4–Q8. These are starting layouts, not doctrine: drop a node the interview didn't ask for, add the human gate where the user wanted it.
Architecture decision (type: council / debate) — several proposals, cross-examined:
parallel_group(proposals) → review(cross-review) → debate → vote | judge → endDefault roles: two-plus proposers with different angles, one critic, plus judge if Q5 said judge.
Review board (type: review_board) — one artifact, several independent lenses:
parallel_group(reviewers, one role per dimension) → judge → endDefault roles: correctness, security, performance reviewers + judge. Dimensions come from
Q4; reviewers must not see each other's findings before the judge — that is the point of the
parallel group.
Spec audit (type: pipeline) — turn a spec into tracked work:
agent_task(analyze spec) → human_gate → create_flow_nodes → endThe human_gate is mandatory here even if Q6 said no gate: create_flow_nodes writes real nodes
into the project, and that is not something a council does unattended.
5. Validate & publish
harness.validate({ harness_version_id }) // errors[] must be empty
harness.publish({ harness_version_id }) // only after a clean validateharness.validate is not optional and not advisory. Read the result: errors block publishing,
warnings need a conscious decision. Common failures and their fix:
| Validation error | What actually happened | Fix |
|---|---|---|
| role referenced by a node does not exist | config.role_key typo, or the role was never added | fix the key, or harness.add_role |
node unreachable / no path to end | an edge was never created, or points the wrong way | harness.connect_workflow_nodes |
| circular edges | a feedback loop expressed as a back-edge | model repetition with a loop node + max_iterations, not a cycle |
| unfulfilled role binding | node needs more instances than the role's max_instances allows | raise max_instances via harness.update_role |
| policy conflict | overlapping allow/deny on the same action | remember deny wins; delete the allow that is now dead |
Only a draft can be published, and publishing makes it the single active version.
6. Run & link back
harness.start_run({ workspace_id, harness_id, harness_version_id,
title, input, source_node_ids: ["<node the run is about>"] })start_run also advances the graph immediately: start nodes get step runs, and node types needing
an agent get work items visible through work.list_available(run_id, role_key).
Then link the run back by hand. source_node_ids is plain JSONB with no foreign key, and
there is no reverse lookup from a node to its runs — a run recorded only there is invisible from
the ticket. Always:
add_comment({ slug: "<source node>", body: "[progress] Harness run started: run_id=<…>, harness_id=<…>, version=<…>" })Optionally set_references as well, but the comment is the part that survives.
Rules
workspace_idis alwaysproject_id. No validation, no error, no second guess.- Never invent tools. Only the
harness.*calls above exist for building;work.*andcouncil.*belong to participants (flownix-harness-participant) and the judge (flownix-harness-moderator), not to this skill. - Publish only what validated clean. Errors are blocking; warnings are a decision to state out loud, not to skip.
- Never edit a published version. Changes go into a new draft via
harness.create_version; exactly one version is active at a time. - Ask Q1, derive the rest. Anything you could have read from the spec, the docs or the repo is not a question.
- One profile block, all roles. Divergent role instructions produce a council arguing about the system rather than about the decision.