coordinator.server¶
coordinator.server ¶
MCP server exposing the task graph as tools to Cursor.
Tools
agents -- Introspect the agent roster (action="list"|"get") create_graph -- Build a DAG of tasks with dependencies get_ready_tasks -- Return unblocked tasks (current wave) claim_task -- Mark a task as running (acquires its path-scoped lease) manage_paths -- Acquire/release fine-grained write leases (action dispatch) submit_result -- Record output, mark done/failed (releases active leases) record_pass -- Advance a multi-pass task's round counter (live progress) get_task_context -- Get predecessor outputs + current task info extend_graph -- Append corrective tasks after failures get_report -- Live status + summary report of the run manage_runs -- Resume, finalize, or clean up interrupted runs
PathLockManager ¶
Process-wide active leases plus run-lifetime path reservations.
Implementers no longer serialize globally — they coordinate on the actual
paths they write. A lease on a path covers that subtree, so two implementers
with disjoint write scopes run concurrently while two that touch the same
path serialize. The whole-workspace key "" conflicts with everything,
which is the conservative default for a task that declares no writes.
Runs with a final memory task retain every acquired path after the active task lease is released. This prevents another run from changing a path before the memory task performs its synchronous code-pin commit. A same-run fix engineer may reacquire a retained path; active task leases still prevent simultaneous implementers within that run.
Holds its OWN lock (a threading.Condition), separate from the server's
_state_lock. Lock ordering is always _state_lock -> this manager and
NEVER the reverse, and the only blocking wait (acquire) is invoked
WITHOUT _state_lock held, so the two locks cannot deadlock.
Source code in coordinator/server.py
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 | |
try_acquire ¶
try_acquire(task_id: str, keys: list[str], *, run_id: str = '', agent: str = '', reserve_for_run: bool = False) -> dict | None
Non-blocking grant. Returns None on success, or holder info on conflict.
Source code in coordinator/server.py
acquire ¶
acquire(task_id: str, keys: list[str], *, run_id: str = '', agent: str = '', reserve_for_run: bool = False, timeout: float = 30.0) -> dict | None
Blocking grant with a timeout. Returns None on success, or holder
info if the lease could not be acquired within timeout seconds.
Must NOT be called while holding _state_lock (it can block).
Source code in coordinator/server.py
release ¶
Release some (or, when keys is None, all) leases held by a task.
Source code in coordinator/server.py
restore_run ¶
Restore persisted run reservations after resume or adoption.
Source code in coordinator/server.py
release_run ¶
Release every path retained for a completed or cleared run.
reserved_for ¶
Return sorted retained paths for persistence and diagnostics.
held ¶
Snapshot of held leases (task_id -> sorted keys) for diagnostics.
list_agents ¶
Return available agent types with one-line descriptions.
Includes built-in agents and any project-local overrides from .cursor/agents.yaml. Each description is suffixed with the agent's workflow role ("implementer", "checker", or "meta"), which determines where the agent may sit in the graph (see create_graph validation rules).
Returns:
| Type | Description |
|---|---|
str
|
JSON object mapping agent names to their descriptions. |
Source code in coordinator/server.py
get_persona ¶
Return the full persona prompt text for a given agent type.
Checks project-local overrides first, then falls back to built-in personas.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
str
|
Agent type name (e.g. "engineer", "reviewer"). |
required |
Returns:
| Type | Description |
|---|---|
str
|
JSON object with the agent name, its workflow role, its full persona |
str
|
text, and |
str
|
coordinator should drive for this agent via Task |
str
|
pass / no re-prompt). May also include |
str
|
configured, and |
str
|
(e.g. Cursor's |
str
|
persona when the host provides it, falling back to the persona otherwise. |
Source code in coordinator/server.py
overview ¶
Use this first when you're unsure how to run a multi-agent task: a compact orientation map.
Read-only. Returns the coordinator's purpose, the standard wave-execution
loop, and a tool -> action index. Authoritative action values also live on
each tool's own action schema enum.
Source code in coordinator/server.py
agents ¶
Use this when you need the agent roster: list every agent, or fetch one agent's full persona before spawning it.
Actions — name(required, optional?):
list(): available agents (built-ins + .cursor/agents.yaml overrides) with
one-line descriptions, each suffixed with the agent's workflow role
(implementer/checker/planner/meta), which gates where it may sit in the
graph. Call while designing the graph.
get(agent): full persona text for one agent plus its role, passes, and —
when configured — model, schema, and native_subagent (a
host-native subagent, e.g. Cursor's bugbot, spawned instead of the
persona and falling back to it). Call right before spawning each subagent.
Returns:
| Type | Description |
|---|---|
str
|
JSON — a name→description map for |
Source code in coordinator/server.py
create_graph ¶
create_graph(tasks: list[dict], goal: str = '', target_entry_id: str = '', run_id: str = '', rigor: str = '', max_extensions: int = 0) -> str
Use this when you've scoped a multi-agent task and are ready to launch it: build the executable task graph (DAG) from a list of task definitions.
Each task dict must have
- alias: str -- local reference name (e.g. "eng", "rev")
- agent: str -- agent type (e.g. "engineer", "reviewer", "critic")
- description: str -- what the agent should do
- depends_on: list[str] -- aliases this task depends on (empty = root)
Optional per-task attributes: model (model override), passes
(self-refinement passes), schema (grounded-extraction rubric name;
requires an enabled capability -- otherwise a hard validation error), and
writes (the implementer task's write scope -- honored here, NOT on
claim_task; see the dedicated section just below). writes is a
first-class per-task field on the SAME footing as model/passes/
schema: set it in each task dict and this tool applies it. It is
REQUIRED in practice for every engineer/scribe task -- omitting it silently
falls back to a whole-workspace lease that blocks every other implementer.
writes declares an implementer (engineer/scribe) task's write scope so
implementers coordinate on a path-scoped lease instead of serializing
globally: tasks with DISJOINT scopes run in parallel; tasks touching the
SAME path serialize at claim time. It is a per-task attribute (set it here,
not on claim_task).
PROTOCOL: you MUST fill writes for every implementer (engineer/scribe)
task -- do not omit it. A whole-workspace lease blocks every other
implementer, so taking one must be a deliberate, explicit choice rather than
an accident of leaving the field blank. Accepted values:
- a list of workspace-relative paths/subtrees (e.g.
``"writes": ["src/api", "docs/api.md"]``) -> leases just those
subtrees; declare disjoint scopes across a wave to run scribes/
engineers concurrently. PREFER this -- keep it as narrow as the task
actually needs.
- an empty list ``[]`` -> dynamic mode: no claim-time lease; the agent
leases paths at write time via
``manage_paths(action="acquire"/"release")``.
- ``["."]`` -> the explicit whole-workspace lease (the conservative,
serialize-everything option). Choose this ONLY when the task may touch
unpredictable paths across the tree; it is the explicit spelling of
the legacy one-implementer-at-a-time behavior.
Omitting writes falls back to a whole-workspace lease for safety, but
that is a backstop, not the intended path -- always pick one of the values
above explicitly. writes is ignored for read-only roles
(planner/checker/meta), which never take a lease. Note: zettelkasten scribes
do not need writes for store writes -- the zettelkasten MCP enforces
per-box locking itself.
The graph is validated before creation
- Must be acyclic
- Must contain at least one implementer (e.g. engineer)
- Every checker task must have an implementer in its dependency ancestry; meta agents (memory, custom role=meta) are exempt
- All depends_on references must resolve
Each call creates an isolated run with its own graph, so multiple graphs
can run concurrently in one process. The returned run_id identifies the
run; pass it to the other run-scoped tools (get_ready_tasks, get_report)
when more than one run is active. Task-scoped tools (claim_task,
submit_result, ...) locate the owning run from the task ID automatically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tasks
|
list[dict]
|
List of task definitions. |
required |
goal
|
str
|
Optional high-level goal for the graph. |
''
|
target_entry_id
|
str
|
Optional memory tree entry ID where results will be recorded. Persisted for the dashboard to position the execution monitor at the correct tree node. |
''
|
run_id
|
str
|
Optional explicit run identifier. Auto-generated when omitted. Must be unique among currently registered runs. |
''
|
rigor
|
str
|
Optional run rigor: "low", "medium" (default), or "high". Sets
how many corrective extension cycles are allowed and the default
self-refinement passes for checker agents. Per-task |
''
|
max_extensions
|
int
|
Optional explicit cap on corrective extension cycles for this run. Overrides the rigor profile's cap when > 0. |
0
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with the run_id, created task IDs, resolved rigor/max_extensions, |
str
|
and a graph summary. Each summary entry echoes the resolved |
str
|
scope (when declared) so you can confirm the per-task lease this tool |
str
|
registered before spawning; an entry with no |
str
|
a whole-workspace lease. |
Source code in coordinator/server.py
1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 | |
get_ready_tasks ¶
Use this to get the next wave: the pending tasks whose dependencies are all satisfied (they can run in parallel).
These are the current "wave" -- all returned tasks can run in parallel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_id
|
str
|
Which run to query. Optional when a single run is active; required to disambiguate when multiple runs are running in parallel. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON list of ready tasks with their id, alias, agent, and description. |
Source code in coordinator/server.py
claim_task ¶
Use this right before spawning a subagent: mark the task as running and take its write lease.
Implementer (engineer/scribe) tasks coordinate on a process-wide
path-scoped write lease shared across all runs, so two implementers with
DISJOINT write scopes run concurrently while two that touch the SAME path
serialize. The lease scope comes from the task's writes:
- omitted -> a whole-workspace lease (the conservative default; reproduces the legacy one-implementer-at-a-time behavior).
- a list of paths -> leases on just those subtrees (disjoint declarations run in parallel).
- an empty list -> dynamic mode: no claim-time lease; the agent leases
paths at write time via
manage_paths(action="acquire"/"release").
If the claim-time lease conflicts with one already held, this returns
{"blocked": true, "held_by": {...}} WITHOUT claiming the task. Spawn the
other ready tasks meanwhile. Active task leases release on submit_result
or manage_paths(action="release"). For a graph with a final memory task,
acquired paths also remain reserved to the run until that memory task
submits; held_by.reserved identifies this case. Read-only tasks
(planner/checker/meta) never take an active lease, so review/critique/test
waves run fully in parallel; the lease is also how the zettelkasten store's
per-box locking is complemented for cooperative engineers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id
|
str
|
The ID of the task to claim. |
required |
run_id
|
str
|
Optional owning run. Inferred from the task ID when omitted. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON confirmation with task details, or a |
str
|
write lease conflicts with another implementer's. |
Source code in coordinator/server.py
1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 | |
manage_paths ¶
manage_paths(action: Literal['acquire', 'release'], task_id: str, paths: list[str] | None = None, timeout_seconds: float = 30.0, run_id: str = '') -> str
Use this when a running task needs to lease or release specific file paths at write time (dynamic write coordination).
Pair an acquire immediately BEFORE writing a shared file with a
release right after, so two agents in the same run never write the SAME
file at once while still progressing on different files. In a memory-backed
run, release drops the active task lease but preserves the run reservation;
overlapping implementers in another run unblock after the memory task
submits.
Actions — name(required, optional?):
acquire(task_id, paths, timeout_seconds?, run_id?): take write leases on
paths — subtree-covering, granted atomically (all-or-nothing) and
deadlock-free regardless of order. BLOCKS until free or
timeout_seconds elapses (waits instead of polling). Returns
{"granted": true, "paths": [...]} or
{"granted": false, "blocked": true, "held_by": {...}} on timeout.
release(task_id, paths?, run_id?): release the task's active leases —
pass paths for just those, omit to release ALL. Active leases
also release automatically on submit_result; a memory-backed
run's reservation remains until its memory task submits.
run_id is inferred from the task ID when omitted.
Returns:
| Type | Description |
|---|---|
str
|
JSON result of the chosen action. |
Source code in coordinator/server.py
submit_result ¶
Record the result of a completed task and advance the graph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id
|
str
|
The ID of the task to submit results for. |
required |
result
|
str
|
The agent's output text. |
required |
success
|
bool
|
Whether the task succeeded (True) or failed (False). |
True
|
run_id
|
str
|
Optional owning run. Inferred from the task ID when omitted. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON confirmation with updated task state. |
Source code in coordinator/server.py
record_pass ¶
Record completion of one self-refinement pass for a running task.
Call this after each pass of a multi-pass agent (reviewer/critic with
passes > 1) -- i.e. after the initial spawn and after each resume.
It advances the task's pass counter so the dashboard shows live round
progress (e.g. "2/3"). This does NOT complete the task; call
submit_result once at the end with the union of findings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id
|
str
|
The ID of the running task. |
required |
summary
|
str
|
Optional one-line summary of what this pass found (shown in the dashboard tooltip). |
''
|
run_id
|
str
|
Optional owning run. Inferred from the task ID when omitted. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with the updated pass progress. |
Source code in coordinator/server.py
get_task_context ¶
Get the full context needed to build a subagent prompt.
Returns the current task's info plus all predecessor outputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id
|
str
|
The ID of the task to get context for. |
required |
run_id
|
str
|
Optional owning run. Inferred from the task ID when omitted. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with "task" (current task info) and "predecessor_outputs" (list). |
Source code in coordinator/server.py
extend_graph ¶
extend_graph(tasks: list[dict], after_task_ids: list[str], context: str = '', run_id: str = '', force: bool = False, reason: str = '') -> str
Append corrective tasks to the graph after one or more failed tasks.
Use this when tasks fail and the coordinator decides to add a fix cycle. The graph grows forward -- nothing is reset or replayed.
The first new task depends on ALL after_task_ids, so it receives context from every predecessor in the failed wave.
OVERSIGHT -- stay on the graph. The extension cap is TIERED, not a single
hard wall:
* Below the soft cap -> the fix cycle is appended normally.
* AT the soft cap -> this returns {"soft_cap_reached": true, ...}
instead of appending. That is an escalation gate, NOT a cue to keep
iterating off-graph. Prefer escalating to the user (a fresh graph or a
human decision). If continuing is clearly right, re-call with
force=true and a reason -- the override is recorded and shown on
the dashboard (forced_extensions), so it is visible, never silent.
* AT the hard ceiling -> a hard error (runaway wall); force cannot pass.
Do NOT fall back to driving raw fix subagents (the Task tool) yourself:
every fix cycle MUST run through the graph so the coordinator records task
activity. A run driven off-graph logs no claim_task/submit_result progress,
so the dashboard loses visibility and, after the idle window, the run is
released as a stale/interrupted record (see manage_runs -- this is by
design). To continue legitimately, force-with-reason here, escalate for a
fresh graph, or resume an interrupted run with manage_runs(action="resume").
Never blindly carry on without a graph. (If you legitimately spawn ad-hoc
helper subagents outside the wave structure, register them with
track_subtask so they stay visible and the run does not go stale.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tasks
|
list[dict]
|
List of task dicts (same format as create_graph). As in
create_graph, every implementer (engineer/scribe) task MUST fill
|
required |
after_task_ids
|
list[str]
|
Task IDs to chain after (typically the failed tasks from a wave). |
required |
context
|
str
|
Failure context to inject into the first new task. |
''
|
run_id
|
str
|
Optional owning run. Inferred from after_task_ids when omitted. |
''
|
force
|
bool
|
Override the soft cap (still bounded by the hard ceiling). Pass
a |
False
|
reason
|
str
|
Why the soft-cap override is justified. Recorded on the run and surfaced on the dashboard. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with new task IDs and extension metadata, OR a structured |
str
|
|
str
|
without |
Source code in coordinator/server.py
1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 | |
track_subtask ¶
track_subtask(run_id: str, label: str, status: str = 'started', agent: str = '', note: str = '', subtask_id: str = '') -> str
Register an ad-hoc "loose" subtask against a run for visibility/recovery.
Use this when an agent legitimately spawns its OWN helper subagents outside the wave/DAG structure (work the coordinator did not create via create_graph / extend_graph). A loose task is OBSERVED, NOT GOVERNED: it gets dashboard visibility and keeps the run's activity clock fresh -- so the run is not released as stale while real work is happening -- but it gets NONE of the structural guarantees of a graph task: no DAG dependencies, no role validation, no write-lease (so concurrent loose writers can still collide), and it does not count against the extension cap.
IMPORTANT: ping a real subtask BOUNDARY -- "started" when you spawn the helper, "completed"/"failed" when it finishes -- NEVER as a periodic keepalive. Faking liveness re-creates the zombie-run problem that release-on-abandon exists to prevent (heartbeat != run progress).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_id
|
str
|
The run this subtask belongs to (required). Must be a run owned by this coordinator process (resume it first if it is interrupted). |
required |
label
|
str
|
Short human description of the subtask. |
required |
status
|
str
|
"started" (default), "completed", or "failed". |
'started'
|
agent
|
str
|
Optional label for the spawned helper (e.g. "engineer"). |
''
|
note
|
str
|
Optional free-form note (e.g. what it produced). |
''
|
subtask_id
|
str
|
Stable id to update an existing loose task across pings. Auto-generated on the first "started" ping if omitted; pass it back on the closing ping. If omitted when closing, the most recently started OPEN loose task with the same label is closed. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with the subtask_id and its recorded state. |
Source code in coordinator/server.py
2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 | |
get_report ¶
Report graph state: summary counts, extension info, and a per-task timeline.
Serves both live status checks (per-task status and dependencies in the
timeline) and the final execution report. Includes any ad-hoc loose_tasks
(registered via track_subtask) separately from the governed timeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_id
|
str
|
Which run to report on. Optional when a single run is active; required to disambiguate when multiple runs are running in parallel. |
''
|
Returns:
| Type | Description |
|---|---|
str
|
JSON report with summary counts, extension info, timeline, and loose tasks. |
Source code in coordinator/server.py
manage_runs ¶
manage_runs(action: Literal['resume', 'cleanup', 'finalize'], run_id: str = '', max_age_seconds: int = 1800, dry_run: bool = False, repo: str = '') -> str
Use this when a coordinator run was interrupted or went stale: resume, clean up, or finalize runs no longer being driven.
A run becomes resumable when its owning process dies (chat closed, MCP restart, crash) or it goes idle (no claim_task/submit_result for COORDINATOR_ABANDON_AFTER_SECONDS, default 2h — its heartbeat freezes and it stops showing "Live"). Going idle is BY DESIGN; the failure mode to avoid is driving work off-graph so the graph goes idle WHILE work continues unsupervised — RESUME (or create a new graph) instead.
Actions — name(required, optional?):
resume(run_id): adopt an interrupted run into this process, then continue
the wave loop (get_ready_tasks → claim_task → spawn → submit_result).
Done/failed tasks keep their results; "running" tasks reset to pending.
No repo — a run resumes only into its own repo's process.
cleanup(run_id?, max_age_seconds?, dry_run?, repo?): remove abandoned runs
from state; no run_id = sweep all incomplete foreign runs whose
heartbeat is older than max_age_seconds. dry_run previews.
finalize(run_id, max_age_seconds?, repo?): mark a stranded run terminally
complete — unfinished (pending/running) tasks become "cancelled",
already-done work and results are preserved.
repo targets a federated peer repo (memory-federation id, or a filesystem
path) for cleanup/finalize only; a peer run still heartbeating is refused.
Returns:
| Type | Description |
|---|---|
str
|
JSON result of the chosen action. |
Source code in coordinator/server.py
2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 | |
create_extraction_graph ¶
create_extraction_graph(sources: list[dict], schema: str, project: str, target_entry_id: str = '', hub_per_source: bool = True, batch_size: int = 0, synthesis_label: str = '', parent_label: str = '', spines: 'list[str] | None' = None, multi_spine: bool = False, synthesize: bool = True, slice_window: int = 0, slice_overlap: int = 0, slice_threshold: int = 0, reextract: bool = False) -> str
Build a grounded-extraction run over a corpus, parameterized by a schema.
Pattern-encodes the fixed pipeline so nobody hand-builds 3xN tasks. For
each source it ingests the document into the zettelkasten and seeds a
per-source hub note (synchronous, tool-side -- this is why the extractor,
a planner, can run first). It then emits per-source
extractor -> scribe -> auditor tasks, each carrying the named
schema (the coordinator flows the expanded rubric into task context),
plus a trailing memory task. Drive the returned run with the normal
wave loop (get_ready_tasks -> claim_task -> spawn -> submit_result).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sources
|
list[dict]
|
List of source dicts. A PROSE source needs |
required |
schema
|
str
|
Registry schema name (see the enabled capability's schemas). |
required |
project
|
str
|
Memory/zettelkasten project the run belongs to. |
required |
target_entry_id
|
str
|
Optional memory tree node for the run's records. |
''
|
hub_per_source
|
bool
|
Seed a per-source hub note during prep (default True). |
True
|
batch_size
|
int
|
0 = one wave of all extractors; N = fan out N at a time. |
0
|
synthesis_label
|
str
|
Label for the materialized structure when the schema
declares a |
''
|
parent_label
|
str
|
Optional PARENT spine label for build-time sub-spine
wiring: declares this (named-schema) spine a sub-spine of another,
writing a primary |
''
|
spines
|
'list[str] | None'
|
Optional list of project spine org ids to SOURCE the run's
structure from (the spine directory, design §6-7). The project's
default spine is ALWAYS auto-included; these are additional
opt-ins, de-duped by org id. When the resolved scope yields a
promoted spine the run attaches extracted claims onto that spine's
EXISTING dimension nodes (via the spine's embedded |
None
|
multi_spine
|
bool
|
Opt-in full multi-spine fill (default |
False
|
synthesize
|
bool
|
Append a trailing |
True
|
slice_window
|
int
|
Target chars per slice for the book-scale fan-out
( |
0
|
slice_overlap
|
int
|
Char overlap between consecutive slices so a quote
straddling a boundary stays whole ( |
0
|
slice_threshold
|
int
|
Char size above which a source is sliced ( |
0
|
reextract
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with run_id, task_ids, the per-task graph, the resolved schema |
str
|
name, the prepared sources (name + hub_id), a graph_summary, and -- |
str
|
when the schema declares a |
str
|
record (synthesis graph + apex/spec ids + dimension tag->node map, |
str
|
plus |
str
|
pre-created during prep and threaded into every task's context. Always |
str
|
includes the scope decision: |
str
|
to resync), |
str
|
resolve), and |
Source code in coordinator/server.py
2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 | |
prep_synthesis_contexts ¶
Register spine extraction contexts so a STANDALONE synthesizer can place.
This is the synthesis-prep half of create_extraction_graph, exposed on
its own so the synthesizer can run OUTSIDE a fresh extraction. A per-source
scribe only attaches claims to the ONE spine its run chose, so each source
ends up with a keyed (project, synthesis_graph) extraction context for
that PRIMARY spine only. note(action="attach") for any OTHER spine then
fails with ExtractionContextNotFound -- the context was never
registered. Inside create_extraction_graph the trailing synthesizer is
saved by build_synthesis_context, which materializes EVERY promoted
spine's structure meta onto the run's sources first; a standalone
synthesizer never hits that path.
Call this BEFORE spawning a standalone synthesizer over an already-extracted
corpus. It materializes each promoted spine layout of every project the
given sources touch -- writing the per-source keyed (project,
synthesis_graph) structure meta (and the apex->hub rollup edges) that lets
cross-spine attach resolve -- and returns the rendered SPINES blocks
to thread into the synthesizer's task context (job 1). The writes are
idempotent (structure-only, non-enforcing: they never set strict, so
they add no write-time rejection and re-running is harmless) and this path
never prunes contexts, so it is safe to run repeatedly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project
|
str
|
The launch project (placed first; other projects the sources belong to are discovered from the manifests and also materialized). |
required |
sources
|
'list[str] | None'
|
Optional list of source GRAPH names (kebab-case ids) to
register contexts for. When omitted, defaults to every source
currently registered in IMPORTANT -- contexts are PER-SOURCE, not global. Passing a subset
registers the spine contexts onto ONLY those sources; every other
source keeps just its primary-spine context, so a standalone
synthesizer trying to cross-spine-place a claim from an
un-prepped source still gets |
None
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with |
str
|
into the synthesizer task -- |
str
|
promoted spine, meaning cross-spine placement has nothing to resolve |
str
|
against), |
str
|
call registered), |
str
|
|
Source code in coordinator/server.py
3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 | |
prep_connection_candidates ¶
prep_connection_candidates(project: str, sources: 'list[str] | None' = None, top_k: int = 8, similarity_threshold: float = 0.5, max_candidates: int = 500, min_cross_degree: int = 1) -> str
Render cross-graph connection candidates for a standalone synthesizer.
The job-2 (flat cross-graph connectivity) counterpart of
prep_synthesis_contexts: that one hands the synthesizer its SPINES
blocks (job 1, cross-spine placement); this one hands it a concrete
worklist of CANDIDATE cross-graph edges to adjudicate (job 2) plus the
island / under-connected audit -- so a whole-corpus pass does not force the
synthesizer to recall the entire corpus from memory (which is why islands
like a lone paper stay islands). Thin renderer over the zettelkasten
suggest(kind="cross-connections") primitive; READ-ONLY (the synthesizer
still writes each edge it judges real via note(action="link", ...,
target_graph=...)).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project
|
str
|
The project whose sources to analyze. |
required |
sources
|
'list[str] | None'
|
Optional subset of source graph names to propose edges FROM (targets still range over the whole project). When omitted, proposes across every source in the project (the whole-project pass). These must already be project members. |
None
|
top_k
|
int
|
Max candidate targets per source note. |
8
|
similarity_threshold
|
float
|
Minimum cosine similarity to propose a pair. Cross-graph cosine is compressed (genuine neighbors ~0.5–0.66), so the default is 0.5; raise for precision, lower for recall. |
0.5
|
max_candidates
|
int
|
Global cap on returned candidate pairs. |
500
|
min_cross_degree
|
int
|
A content note with fewer cross-graph edges than this
is reported |
1
|
Returns:
| Type | Description |
|---|---|
str
|
JSON with |
str
|
thread into the synthesizer task; |
str
|
|
str
|
output), |
Source code in coordinator/server.py
3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 | |