Sections
On this page
- Harness Council Participant
- Workflow
- 1. Discovery — find work assigned to your role
- 2. Claim — take a lease on one item
- 3. Read the context — right after the claim
- 4. Do the work by node type
- 4a. Debate — proposals, critiques, responses
- 4b. Vote — by the ballot from the context
- 5. Complete — task, review and debate work
- 6. Fail honestly
- Deliberation id
- Error handling
flownix-harness-participant
Use when you have been assigned a role in a running Flownix harness — council, review board or debate — and must do your turn. Triggers on "you are the architect in run …", "participate in the council", "claim your work item", "ты участник
Harness Council Participant
You are one agent instance filling a role (e.g. architect, reviewer, security) inside a running harness. This
skill drives the participant lifecycle: find work, claim it, read its context, do the work, and close it the way the
node type expects — work.complete for task, review and debate work; a vote for vote work.
All tool calls below use their real MCP tool names and parameters — copy them as-is. Everything you need comes from MCP: do not ask a human for the run input, your instructions or the peers' proposals, and do not look for them in Flownix node comments.
If you hold the judge role, use flownix-harness-moderator instead.
Workflow
1. Discovery — find work assigned to your role
work.list_available(run_id: "<run_id>", role_key: "<your_role_key>")Returns {work_items: [...]} — unclaimed work items with id, workflow_node_id, role_key, input_artifact_ids and, for debate,
vote and judge nodes, deliberation_id. If empty, there is nothing to do yet — poll again later or stop.
harness.get_run(run_id) shows where the run stands (status, steps and their outputs) if you need to know why.
2. Claim — take a lease on one item
work.claim(work_item_id: "<id>", agent_id: "<your_agent_id>")Only one agent holds a work item at a time. The response carries lease_expiry and your role
(system_instructions, objective, responsibilities, prohibited_actions, output_schema) — follow the
prohibited actions, and shape your output to output_schema: the server checks it on work.complete.
Use the same agent_id in every later call on this work (author_agent_id in council.submit_proposal, council.submit_claim, council.submit_critique, council.respond_to_critique; agent_id in
council.cast_vote / council.abstain): the runtime ties your council calls to your lease by that id.
Lease. Its length is the node's config.lease_seconds (default 300 s, clamped to 60–3600). Call
work.heartbeat(work_item_id: "<id>")well before lease_expiry — e.g. every third of the lease — while you work; each heartbeat renews the lease for the
same length. A lease that runs out is reclaimed by the background reconciler (every ~30 s): the item goes back on the
queue and spends an attempt, exactly as work.fail would.
3. Read the context — right after the claim
work.get_context(work_item_id: "<id>")One call returns everything the work needs:
run—id,title,status,input(the run's input) andsource_node_ids(the Flownix nodes the run is about;get_nodethem);node—name,type,config;config.descriptionoften holds the step instructions;role— the same role block as inwork.claim;work_item— includinginput_artifact_ids(theresult_artifact_idsof the predecessor steps whose edges led here) anddeliberation_id;predecessors— each predecessor step'snode_name,node_type,status,iterationandoutput(including itsoutcome,decision_id,winner_proposal_idand so on);all_steps— present when the version's context policy asks for it: the outputs of every completed step of the run, in completion order;context_truncated/omitted_workflow_node_ids— the policy'smax_tokensdropped the earliest step outputs; read them withharness.get_run(run_id)if you need them;ballot— only for avotenode:ballot_id,method,quorum,threshold,allow_abstain,statusandoptions(proposal_id+title).
What is included follows the version's context policy: a policy may leave out run.input or role, add
all_steps, or strip output keys listed in its exclude. Without a policy you get input, role and predecessors.
input_artifact_ids are references (a commit, a node slug, a URL, decision:<id>) — dereference them yourself
(get_node, the repository, council.get_deliberation). On a council node read the deliberation itself:
council.get_deliberation(deliberation_id: "<deliberation_id>")It returns subject, phase, round / max_rounds, every proposal with its claims and evidence, every critique with
its responses, ballots with votes (hidden on an anonymous ballot until it closes) and decisions.
Pull the project's own knowledge into your analysis — workspace_id on the run is the Flownix project_id:
rag_context(project_id: "<workspace_id>", query: "<the question the council is deciding>")
rag_query(project_id: "<workspace_id>", query: "<prior decisions on this subsystem>", k: 5)Hits are leads — get_node anything you intend to cite, and cite the node, not the chunk. See flownix-rag-search.
4. Do the work by node type
node.type | What you do | How the work item closes |
|---|---|---|
agent_task, review | the task per config.description and your role | work.complete (step 5) |
debate | proposal, critiques, responses (step 4a) | work.complete (step 5) |
vote | vote by the ballot from the context (step 4b) | council.cast_vote / council.abstain — no work.complete |
judge | not yours — flownix-harness-moderator | council.submit_judgement |
Before any create_flow_nodes node the run stops at a human_gate; your step's output must carry the
flow_nodes list (step 5) if your role's instructions say so.
4a. Debate — proposals, critiques, responses
Submit your position:
council.submit_proposal(
deliberation_id: "<deliberation_id>",
author_agent_id: "<your_agent_id>",
title: "Short descriptive title",
summary: "1-2 sentence summary",
body: "## Approach\n...\n## Trade-offs\n...\n## Risks\n...",
confidence: 0.85
)Back it with claims and evidence:
council.submit_claim(proposal_id: "<id>", author_agent_id: "<your_agent_id>", text: "...", type: "fact|assumption|prediction|requirement|risk|recommendation", confidence: 0.9)
council.attach_evidence(claim_id: "<id>", type: "code_reference|document|flow_node|mcp_resource|tool_result|external_url|user_statement|test_result", uri: "...", content_snapshot: "...", metadata: {...})A Flownix node you found via rag_query and verified with get_node is type: "flow_node" evidence — its slug as
uri, the verified node text (not the RAG chunk) as content_snapshot.
Cross-review the peers' proposals you read in council.get_deliberation:
council.submit_critique(
deliberation_id: "<deliberation_id>",
author_agent_id: "<your_agent_id>",
target_type: "proposal|claim|critique",
target_id: "<id>",
category: "correctness|security|feasibility|cost|complexity|missing_evidence|contradiction|scope|other",
severity: "info|minor|major|blocking",
summary: "One line summary",
body: "Detailed critique with reasoning and a suggested resolution (Markdown)"
)Answer critiques on your proposal:
council.respond_to_critique(
critique_id: "<id>",
author_agent_id: "<your_agent_id>",
body: "Your response",
action: "accept|partially_accept|reject|revise_proposal|request_evidence"
)If the critique changes your position, follow up with
council.revise_proposal(proposal_id: "<original_id>", title: "...", summary: "...", body: "...", confidence: 0.8) —
it creates a new version that supersedes the original.
Rounds. When every work item of the debate node in the current round is closed, the runtime checks the
deliberation: while any major or blocking critique is still unanswered (status open) and round < max_rounds,
it opens the next round and creates new work items for the role — claim them like any other work. Any response
moves a critique out of open. Otherwise the step completes with outcome settled (nothing left open) or
rounds_exhausted (limit reached with critique still open). So answer every major/blocking critique on your
proposal before completing, or the council spends another round on it. Severity is yours to choose honestly —
major/blocking extends the debate.
Several nodes can share one deliberation through config.deliberation_key (e.g. parallel security and
correctness debates and the final vote/judge): the same deliberation_id arrives on all their work items, and
all proposals and critiques land in one place. A vote offers exactly the proposals present when it starts, so an
idea without council.submit_proposal cannot be voted on.
4b. Vote — by the ballot from the context
Take ballot.ballot_id and pick one of ballot.options[].proposal_id from work.get_context; read the proposals
behind them in council.get_deliberation. Then:
council.cast_vote(ballot_id: "<ballot.ballot_id>", agent_id: "<your_agent_id>", option_id: "<proposal_id>", rationale_summary: "...", confidence: 0.9)(score for method: score, rank for ranked_choice.) If you genuinely cannot form an opinion and
ballot.allow_abstain is true, abstain instead — it counts toward quorum:
council.abstain(ballot_id: "<ballot.ballot_id>", agent_id: "<your_agent_id>", rationale_summary: "insufficient evidence")The vote is the result of your work. council.cast_vote / council.abstain from the agent_id that holds the
lease completes your vote work item and advances the run. Do not call work.complete afterwards — it is refused
as an operation on an item you no longer hold. With a different agent_id the vote counts but your work item stays
leased until the lease expires.
The ballot closes on the first of: votes plus abstentions reach quorum, every vote work item is closed, or
config.timeout_seconds passes. Remaining vote work is then cancelled, and the step's outcome is winner, tied,
no_quorum or no_consensus. A missing ballot or status other than open means there is nothing to vote on
(for example the step failed with ballot.no_options) — report it with work.fail rather than inventing a
ballot_id.
5. Complete — task, review and debate work
work.complete(
work_item_id: "<id>",
result_artifact_ids: ["<proposal_id>", "commit:abc", "AF-TASK-12", ...],
outcome: "approved", // optional, ^[a-z0-9_]{1,32}$
output: { ... }, // optional structured output
usage: { input_tokens: 12000, output_tokens: 3000, cost_usd: 0.42 } // optional
)result_artifact_idsbecomeinput_artifact_idsof the next steps — list what you produced (proposal ids, commits, node slugs, URLs).outcome— set it whenever branching depends on the result: outgoing edges andloopexit conditions select onoutcome(e.g.approved/changes_requestedon a review). Only lowercase letters, digits and underscore, 1–32 characters; anything else is refused withInvalidArgument. Take the exact values fromnode.config.description, your role's instructions or the conditions you were told about — an outcome no edge expects routes nowhere.output— a JSON object merged into the step output (result_artifact_idsandoutcomein it are ignored). It must match your role'soutput_schemaand, on a step leading intoend, the version'soutput_schema; a missingoutputis checked as{}. A downstreamcreate_flow_nodesnode reads its list from here:{"flow_nodes": [{"key": "k1", "kind": "epic|feature|plan|task", "title": "...", "content": "...", "parent": "<node slug or an earlier key>", "components": [...], "tags": [...]}]}.usage— tokens and cost you spent on this work. It is added to the run's usage and checked against the budget (see Error handling). The server does not verify it; report honestly.
If a require_approval tools policy covers work.complete for your role on a task/review node, the call succeeds,
but the step waits for a human decision (the run goes waiting_for_user) — nothing more for you to do on that item.
6. Fail honestly
work.fail(work_item_id: "<id>", error_message: "what went wrong", usage: {...})A plain work.fail spends one attempt out of max_attempts: while attempts remain the item returns to the queue and
the step stays alive. Once attempts run out the step fails and the run is marked failed. Add no_retry: true only
when the task is impossible as specified (bad input, contradictory instructions, missing context no agent can
recover) — it skips the remaining attempts and fails the step, and with it the run, immediately. Do not use
no_retry when you merely failed at it.
Deliberation id
Work items from debate, vote and judge nodes carry a deliberation_id — the id the council.* tools take.
Read it off the work item or work.get_context; never invent one and never pass the run id in its place. A work item
without a deliberation_id came from a plain node, and council.* does not apply to it.
The runtime opens the deliberation when the node starts. Nodes with the same config.deliberation_key in one run
share it; a node without a key gets its own. A shared deliberation outlives any single node and is resolved by a
decision — a vote with a winner or a judge's judgement; on tied, no_quorum or no_consensus it stays open for
the next node (usually judge).
Error handling
- Transient failures (network error, timeout): retry the same call while you still hold the lease.
policy.denied(PermissionDenied: policy.denied: policy <id> denies <tool> for role "<role>"): atoolspolicy of the harness version forbids thatwork.*/council.*call for your role. Do not retry it and do not work around it with another agent id. Continue without that action if the work still makes sense; otherwisework.failwith the denial aserror_messageandno_retry: true— every retry would hit the same policy.budget.exceeded: afterwork.complete/work.failthe reported usage is checked against the run budget (max_tokens,max_cost_usd;max_work_itemsandmax_run_duration_secondsare checked by the runtime). An exhausted limit fails the whole run with reasonbudget.exceeded— your completion is recorded, but the run does not advance. Stop working on that run;harness.get_runshowsusageandremaining.- Lease expired: if
work.heartbeatfails ("lease may have expired"), stop — the item may already be back on the queue or claimed by someone else. Do not submit results for a work item you no longer hold. - Work item unavailable (
work item is not available (already claimed, already closed, or its run is no longer active)): the item was taken, already closed (e.g. by your vote), or its run iscompleted,failedorcancelled. This is a normal answer, not an outage — do not retry. - Invalid
outcome/output:InvalidArgument— fix the value and callwork.completeagain; the lease is still yours. output.schema_violation(InvalidArgument: output.schema_violation: output does not match role <key> output_schema: /findings: required property is missing; …): each entry is a JSON pointer into youroutputand the reason. Add or fix exactly those fields and callwork.completeagain — the lease is still yours and nothing was recorded. Do not strip the schema's intent by sending placeholders; if you genuinely cannot produce a field,work.failwith the reason.- Human gates: a run in
waiting_for_userwaits for a person.harness.list_pending_approvals(run_id)shows what it waits for; agents cannot answer approvals — tell the user to decide in the Flownix web app.