Sections
On this page
- Flownix Basics
- The hierarchy
- Status lifecycle
- The ticket is your memory
- System components — binding is mandatory
- The rule
- Tools
- MCP tool reference
- Deltas: discard records a decision, delete removes a mistake
- Documents: archive, not delete
- Dependencies: mind the direction
- Slugs and navigation
- RAG — semantic search over the project
- The action log
- Presence
- Harness — multi-agent workflow orchestration
- Concepts
- Build → publish → run
- Harness tools
- The project config file
- Spec mode and doc deltas
- The approval gate
- Spec-first: the document before the tasks
- The spec format
- Golden rules
flownix-basics
Read FIRST before any Flownix / AI-Flow work over MCP, and before any other flownix-* skill. Use when the request mentions Flownix, AI-Flow, a node slug (AF-TASK-12, ACME-FEAT-3), a ticket/задачу/тикет/эпик/фичу in the tracker, the .flownix
Flownix Basics
Flownix is a hierarchical product-planning system, reached only through the Flownix MCP server. Every other Flownix skill builds on this one.
The hierarchy
Every unit of work is a node, in a strict tree:
project → epic → feature → plan → task
- project — the product/initiative (
create_project). - epic — a large body of work.
- feature — a shippable capability.
- plan — an implementation plan for a feature.
- task — a concrete executable unit (the leaf where real work happens).
get_tree shows the structure, list_nodes lists by kind/parent, get_node loads one node
with content, comments, tags and dependencies.
Status lifecycle
Drive it explicitly with update_node_status:
backlog → planning → in_progress → testing → regression → done ↘ blocked ↗
- backlog not started · planning being decomposed · in_progress active · testing implemented, under verification · regression verification found issues · done complete and verified · blocked cannot proceed (always explain in a comment).
update_node_progress sets 0..100 for finer tracking on long work.
The ticket is your memory
You are stateless between sessions. The node IS your durable memory.
- Read before acting —
get_nodefor content and prior comments. Never assume. - Write down what you did —
add_commentafter any meaningful step. - Keep status/progress honest — the board must reflect reality.
Comment prefixes, so logs stay scannable:
[plan] · [progress] · [decision] · [blocker] · [review] · [done]
System components — binding is mandatory
Besides the work tree, a project describes the system being built: its services, apps,
libraries and infrastructure. Each is a component with its own slug (AF-COMP-3).
A component is not a unit of planning: it executes nothing, never moves to done, and is
outside the epic→feature→plan→task tree. It groups work and reflects its state. Its own
lifecycle is lifecycle (planned/active/deprecated), never status.
Not a tag: a tag has no description, no tree, no repository path, no progress and no page.
| Field | Meaning |
|---|---|
title | the name it has in the repository: core-service |
description | purpose, boundaries, entry points |
component_kind | service | app | library | infra | integration |
repo_path | services/core-service |
lifecycle | planned | active | deprecated |
parent | parent component; components form a tree (monorepo → group → service) |
The rule
Every node and every doc you create or close must be linked to at least one component.
- Links are many-to-many — one node may touch several components, and an architectural spec describing gateway and core-service belongs to both. Link all of them, not the first.
- Docs and specs are bound too, not only tasks: a doc node without a component is unreachable from the part of the system it documents.
- No suitable component? Create it with
create_component(title= the real name in the repo,repo_path,component_kind,parentwhen it nests) and link to that. Never leave work unlinked because the inventory is incomplete — an unlinked node is invisible in the system view and its progress is counted nowhere. - Never invent a component that does not exist in the repository. If you cannot point at the code it stands for, you are inventing structure — check the repo first.
- The exception is a genuinely cross-cutting node that belongs to no part of the system
(a release checklist, a process ticket). Say so in a
[decision]comment rather than leaving it silently unlinked.
Infrastructure and external services are components too, not tags: postgres,
qdrant, the cluster, the pipeline (infra), the LLM provider, object storage
(integration). Migrations, reindexing, deploy and quota work belongs to them, and that is
what makes "everything we did to the database" a question with an answer. The languages and
libraries a component is written in stay a tag plus a line in its description.
For a first pass over a project that has none — code and stack alike — use map-components.
Tools
| Tool | Purpose |
|---|---|
list_components | Components of a project, flat with parent_id, each with its progress |
get_component | One component with children, linked tasks, docs and reviews |
create_component / update_component | Create and edit (project_id, title, …) |
set_node_components | Replace the set of components a node is linked to |
set_review_components | The same for a review |
list_components({ project_id }) // always check first
create_component({ project_id, title: "core-service", component_kind: "service",
repo_path: "services/core-service" })
create_node({ ..., components: ["AF-COMP-3", "AF-COMP-7"] }) // link at creation
set_node_components({ node: "AF-TASK-12", components: ["AF-COMP-3"] }) // replaces the set
list_nodes({ project_id, component: "AF-COMP-3" }) // direct links only
list_docs({ project_id, component: "none" }) // what is still unsorted
rag_query({ project_id, query: "…", component: "AF-COMP-3" })Two things that are easy to get wrong:
- Lists filter by DIRECT links; progress counts recursively down the component tree —
work on
gatewayshows up inservicesand in the monorepo above it. Deliberate: a list answers "what did I link here", progress answers for a part of the system as a whole. set_node_componentsreplaces the set, it does not add. Pass the existing links too unless you mean to drop them.
Progress is computed, never set. list_components/get_component return percent with
total, done, a status breakdown and counts of linked docs and reviews.
MCP tool reference
Most tools accept node_id or slug. A slug alone is enough — it carries the project key
(MF-TASK-274 → project MF) and is unique across the org, so project_id is optional
wherever a slug is accepted. Prefer slugs.
| Tool | Purpose |
|---|---|
list_organizations / create_organization | List orgs the user belongs to / create one |
list_projects | Projects in an organization (org_id) |
create_project | Create a project (org_id, name, key, spec Markdown) |
get_tree | Full hierarchy of a project (project_id) |
list_nodes | List nodes (project_id, optional kind, parent_id/parent_slug, component, ticket_ref — exact match on an external tracker ticket) |
get_node | One node + content, comments, tags, deps, child_ids; returns commit_hash, ai_model, agent_name, author_email, timestamps |
get_node_by_slug | Same, slug-only (slug, optional project_id) |
create_node | Create epic/feature/plan/task (project_id, parent_id/parent_slug, kind, title, content, optional tags, components, ai_model, agent_name, ticket_ref) |
update_node_content | Update title/content (optional commit_hash, ticket_ref) |
update_node_status / update_node_progress | Set status / progress 0..100 |
add_comment | Append an agent comment (Markdown body) |
add_tag | Tag a node |
add_dependency / remove_dependency | Edge read as "from_node depends on to_node": to_node is the blocker |
get_dependencies | depends_on (blockers) and blocks (waiters) for one node |
set_references / get_references | Set (overwriting) / read cross-references (ref_ids, ref_slugs; a ref may live in another project) |
add_relation / remove_relation / get_relations | Typed bidirectional relations (relation_type) |
list_components / get_component / create_component / update_component | System components — see above |
set_node_components / set_review_components | Replace a node's / review's component set |
get_project_policy / set_project_policy | Read / set spec_mode (off/strict), require_proposal_approval and spec_first; org members only, never silently |
validate_spec | Check a spec against the OpenSpec format — by doc_slug or by raw body; changes nothing |
propose_doc_delta | Propose the complete next body of a doc, linked to a source task/plan |
update_doc_delta / submit_doc_delta | Edit a draft (or rejected) delta in place with the full body / send a draft to review — the only draft → pending transition |
get_doc_delta | One delta in full, with its round history. The list call does not return rounds |
list_doc_deltas / apply_doc_delta | List revisions (each carries review_status) / apply one with optimistic version checking |
approve_doc_delta / reject_doc_delta | Humans only. A call from an agent session is refused — see The approval gate |
discard_doc_delta / delete_doc_delta | Record a rejected revision / remove a garbage row (applied is refused) |
declare_no_spec_impact | Record why a task/plan changed no documented behaviour |
create_doc_node / update_doc_content / list_docs | Doc nodes |
archive_doc / restore_doc / list_archived_docs | Archive cascades down the subtree; restore returns exactly one doc |
rag_query / rag_context / rag_status | Semantic search, assembled context block, index freshness. Read-only |
list_action_events | Project audit trail: who did what, when, through which channel. Read-only |
Deltas: discard records a decision, delete removes a mistake
discard_doc_delta = "we considered this and decided against it" — the row stays, marked
discarded, and the history shows the proposal was handled. This is the normal way to say no.
delete_doc_delta = "this should never have existed": a duplicate, an accidental proposal.
An applied delta cannot be removed at all — it is spec history; propose a new one on top.
Documents: archive, not delete
delete_node on a doc is irreversible and takes its revisions with it. Use archive_doc: the
doc leaves the tree, list_docs and rag_query, but revisions, comments and references stay.
Archiving cascades down the subtree, restoring does not — read the archive response, it names
every slug that went along. A doc missing from list_docs is not proof it was deleted: check
list_archived_docs first.
Dependencies: mind the direction
Always read as "from_node depends on to_node" — to_node is the blocker.
// "Register the skill" can only be done after the skill file exists:
add_dependency(from_slug: "AF-TASK-200", to_slug: "AF-TASK-199")
// ^ waits ^ blocker, done first
get_dependencies(slug: "AF-TASK-200") // → depends_on: [AF-TASK-199], blocks: []The common mistake is calling it in the order the work happens and getting the edge backwards.
Say the sentence out loud: the node you name first is the one that waits.
get_node(...).dependency_ids likewise lists blockers. Dependencies are the ordering
graph; typed relations (add_relation) express semantics — don't duplicate one as the other.
Slugs and navigation
get_node(node_id: "abc123") // all three are equivalent
get_node(slug: "ACME-TASK-1", project_id: "…")
get_node(slug: "ACME-TASK-1")
create_node(parent_slug: "ACME-PLAN-1", kind: "task", title: "…", components: ["AF-COMP-3"])
set_references(slug: "MF-FEAT-68", ref_slugs: ["MF-DOC-7", "AF-TASK-217"])An unresolvable slug returns NotFound naming it — fix that slug rather than hunting for IDs.
get_node returns child_ids, so you can walk the tree without get_tree.
RAG — semantic search over the project
Every project has a semantic index built automatically from node content and comments:
rag_query— ranked hits by meaning (slug,kind,title,content,status,score); narrow withkind_filterorcomponent.rag_context— top chunks pre-assembled into one Markdown block for prompt injection.rag_status—total_chunks,last_indexed_atand the sync breakdown (pending/in_progress/synced/failed/skipped).
Reach for RAG whenever you need to find something. Before planning a feature, before creating any node, when picking up a task whose context you don't hold, when the user names work by description rather than slug, and whenever you are about to guess. Two or three queries with different phrasings routinely prevent a duplicate ticket.
Recall, not truth — hits are leads, possibly seconds stale. Confirm with get_node.
Sync is automatic; there are no indexing tools. A node you just wrote may not be
searchable yet, so never verify your own write with rag_query — use get_node. Empty
results mean "not found right now": check rag_status before concluding something doesn't
exist. On rag-service unavailable, degrade to structural tools and say so.
Query patterns and per-tool arguments: flownix-rag-search.
The action log
Every significant mutation is recorded project-wide, independent of any node's comments.
list_action_events(project_id, limit: 10) — or scoped with entity_id — tells you what
happened recently and who did it. Useful when picking up work another agent left mid-flight.
Comments are the why; the action log is the what/when/how.
Presence
While connected over MCP you appear in a live presence panel — nothing is required of you:
the session registers on connect, every call keeps it alive, and the task is inferred from the
node_id/slug you pass. One optional call makes the row informative:
session_announce({ agent_name: "Claude Code", ai_model: "Claude Opus 5",
host: "…", cwd: "…", git_branch: "…" })session_report (tokens/cost, normally from a statusLine script) and session_end also exist.
Reads carrying no node (rag_query, get_tree) keep the session alive but don't change which
task you're shown on — searching is not switching.
Harness — multi-agent workflow orchestration
A separate subsystem of reusable multi-agent workflow templates (council, review board,
debate) that you define, publish and run. Its MCP tools use dotted names
(harness.create, work.claim, council.submit_proposal) — distinct from the
underscore-named node tools. workspace_id on harness tools is the Flownix project_id;
there is no separate workspace entity and no FK validating it, so getting it right is on you.
Concepts
- Harness definition (
harness_id) — the named template. - Version (
harness_version_id) — a draft or published snapshot: graph, roles, policies, schemas. Sequential; one published at a time. - Role — objective, system prompt, responsibilities, prohibitions, instance bounds.
Referenced by
keyfrom workflow nodes. - Workflow node — one step;
type∈agent_task,parallel_group,review,debate,vote,judge,human_gate,transform,create_flow_nodes,condition,loop,end. - Policy — category (
tools/data_access/voting/budget/execution/approval/retention/privacy) + effectallow/deny/require_approval;denywins. - Run (
run_id) — one execution; starting it creates work items for agent-needing nodes. - Work item — a claimable unit with a lease.
source_node_ids/result_node_idsare plain JSONB, no join table — if you need the run↔node link discoverable, record it yourself (add_commentwith therun_id, orset_references). - Deliberation — proposals, claims with evidence, critiques (info/minor/major/blocking), revisions, then a ballot or a judgement.
Build → publish → run
harness.create(workspace_id,name,type)harness.create_version(draft)harness.add_role× N,harness.add_workflow_node× N,harness.connect_workflow_nodesharness.set_policy(optional)harness.validate— missing roles, disconnected nodes, cycles, policy conflictsharness.publishharness.start_run— passsource_node_idsto link it to the task/feature it operates on
harness.clone deep-copies a definition as a new draft. harness.export produces a ZIP
(harness.yaml, roles.md, rules.yaml, workflow.json, prompts, README) — URL expires in 1 hour.
To build one: harness-blueprint. Once a run is live: flownix-harness-participant
(you hold a role) or flownix-harness-moderator (you hold judge).
Harness tools
| Tool | Purpose |
|---|---|
harness.create / harness.get | Create a definition / read it with version details |
harness.create_version | Open a new draft version |
harness.add_role / harness.update_role | Add or edit an agent role |
harness.add_workflow_node / harness.connect_workflow_nodes | Build the graph |
harness.set_policy | Add a policy |
harness.validate / harness.publish | Check a draft / make it active |
harness.clone / harness.export | Deep-copy / export as ZIP |
harness.start_run | Start a run of a published version |
work.list_available / work.claim / work.heartbeat | Find, lease, extend |
work.complete / work.fail | Finish with result artifacts / fail with an error |
council.submit_proposal / council.revise_proposal | Submit / supersede a proposal |
council.submit_claim / council.attach_evidence | Attach a claim / evidence |
council.submit_critique / council.respond_to_critique | Critique / accept-reject-revise |
council.cast_vote / council.abstain | Vote on a ballot |
council.submit_judgement | Issue the authoritative decision as judge |
The project config file
Every skill reads project context from .flownix in the working directory, created by
flownix-init:
spec_mode: "off"
org: { id: "<org_id>", name: "<org_name>" }
project: { id: "<project_id>", name: "<name>", key: "<KEY>", spec: "<markdown>" }Read it before any MCP call. The product was renamed, so both names are accepted:
cat .flownix 2>/dev/null || cat .ai-flow.flownixexists → use it (if.ai-flowis also there, say once it is now redundant).- Only
.ai-flow→ use it, and say once: "the config is now.flownix; rename when convenient — the old name keeps working." - Neither → run flownix-init.
Once per session, not per call. Parse the YAML for project_id, org_id, project.key —
never ask the user for IDs. If parsing fails or the project_id no longer resolves, re-run
flownix-init; new files are always written as .flownix.
Spec mode and doc deltas
off keeps the ordinary workflow. In strict, a task or plan reaches done only after an
applied doc delta sourced from that node, or an explicit declare_no_spec_impact with a real
reason. A delta is the complete resulting document, never a patch or excerpt: reread the
doc, preserve everything still true, make the change, propose and apply that full body. That
is what makes the server's revision history and diffs reliable.
The approval gate
Every proposed delta carries a review_status — draft, pending, approved or rejected — separate
from its state (proposed/applied/discarded). state says whether the change reached the
spec; review_status says whether a human looked at it. A delta is routinely proposed and
already approved: that pair is what "you may start now" looks like.
When the project has require_proposal_approval on (get_project_policy tells you, alongside
spec_mode), the server refuses two things until the covering delta is approved:
- moving the covered task or plan into
in_progress, and apply_doc_delta.
Both refusals come back as FailedPrecondition prefixed proposal_not_approved. The gate
looks up the tree: a delta proposed from a feature or plan blocks the tasks underneath it. It
does not require that a proposal exist — it only refuses to let one past review.
draft is the odd one out: it means nobody has been asked yet. It appears only in projects with
spec_first on, it stands in no queue and holds no gate, and it becomes a question to a human
only when submit_doc_delta makes it pending. See Spec-first below.
You cannot approve anything. approve_doc_delta and reject_doc_delta refuse every call
from an agent session with PermissionDenied; the tools are listed so you can name the step you
are waiting on. When a delta is pending, stop and tell the user which delta needs approval,
then wait. Do not re-propose it, do not apply it, do not move the task into in_progress, do
not delete it and start over — a rejected delta can still be approved later. When it comes back
rejected, read review_note, rebuild the spec on that feedback, and propose a new delta.
The full procedure for authoring a change proposal is its own skill: flownix-write-spec.
Spec-first: the document before the tasks
A third policy flag, spec_first, reverses the order of work: the feature's document is written
and accepted before any task is created under it. With it on, two things change.
- A delta is born
review_status: draft— a working copy that asks nobody anything — and reaches a human only throughsubmit_doc_delta. - Creating (or moving) a
taskorplanunder a feature is refused withFailedPreconditionprefixedspec_first_not_approveduntil that feature has an approved delta, or has declared no spec impact. Unlike the entry gate above, here the absence of a proposal is itself the violation.
spec_first only works together with spec_mode: strict and require_proposal_approval; the
server refuses to enable it without them, and refuses to disable either while it is on. It is
off by default, and with it off nothing above applies.
The procedure is its own skill — spec-first-change. It is not described here on purpose: this file is the map, and a second full account of the flow would be the copy that drifts.
The spec format
A spec describes observable behaviour, not a plan of work, and it is written in the OpenSpec
format — the only accepted way to record behaviour in this system. Not a preference, not a
default you may fall back from: freeform Markdown in a doc node is a defect, validate_spec
reports it, and in spec_mode=strict any delta that adds a new violation is refused.
# <Document title>
## Purpose
One or two sentences: what this part of the system is for.
## Requirements
### Requirement: <name>
The system SHALL <one observable behaviour>.
#### Scenario: <the case this covers>
- GIVEN <starting condition>
- WHEN <what happens>
- THEN <the observable result>One requirement, one behaviour, one SHALL; every requirement needs at least one scenario; the
how stays in the plan. The rules, validate_spec, propose_doc_delta and the approval gate
are owned by one skill — flownix-write-spec. They are not restated in each skill on purpose:
the same block used to sit in five of them, and five copies drift.
Golden rules
- Read
.flownixfirst (falling back to.ai-flow); discover IDs, never invent them. - Link every node and doc to at least one component; create the component if it is missing. See System components above.
- One task = one executable unit. Too big → it is a plan/feature; split it.
- Search before you create and before you guess —
rag_query/rag_contextis the default way to find anything; confirm hits withget_node; if the index is cold (rag_status), say so instead of concluding nothing exists. - Set
ai_modelandagent_nameoncreate_node(e.g."Claude Opus 5","Claude Code"). - Set
commit_hashviaupdate_node_contentafter committing the work. - Write specs as
## Purpose/### Requirement:/#### Scenario:— this format is mandatory, not preferred. Runvalidate_specbefore proposing a delta. See The spec format above, and flownix-write-spec for the whole procedure. - Never start work on a task whose covering delta is not
approvedwhen the project hasrequire_proposal_approvalon. Stop and ask the user; you cannot approve it yourself. See The approval gate. - Never create tasks under a feature before its document is accepted when the project has
spec_firston — the server refuses, and the refusal is not a bug to route around. See Spec-first and spec-first-change. - Set
ticket_refwhenever an external ticket is mentioned (Jira/YouTrack/…), oncreate_nodeorupdate_node_content. Optional in the schema, mandatory in practice — but never invent one.list_nodes(ticket_ref: …)finds everything tied to a ticket. - After finishing a feature or epic, write the doc and link it back in the same step —
create_doc_node, thenset_references(slug: "<FEAT slug>", ref_slugs: ["<DOC slug>"]). Nobody finds a doc by browsing the doc list; they arrive from the feature. Creating the doc and forgetting the reference is the common failure. - Cross-link with
set_references, not with text incontent— references render as clickable links on both nodes. Each call replaces the set, so pass the full list. - Write plans, tickets and docs in the language of the user's prompt. Russian prompt → Russian ticket.
- Harness tools are dotted and take
workspace_id(=project_id) — don't mix namespaces.