The constitution is the layered, file-backed prompt Stringwork prepends to every worker. It is the answer to the question "what does my team always want every agent to know before doing anything?".
It is inspired by the spirit of unclebob/swarm-forge: keep team rules in plain markdown, in version control, and let the orchestrator do the work of injecting them.
- Preamble in coordination tools. When a worker calls
claim_nextorget_work_context, the response includes a short preamble listing the resolved files in priority order. Workers are instructed to read them before doing anything else. The preamble is omitted onclaim_next dry_run=truepeeks (those return single-line JSON for callers that parse it programmatically); it appears on the actual claim. - Inlined into spawn prompts. When the server spawns a worker for a task, the full text of every resolved file is embedded into the spawn prompt before the task description block. This means the worker sees the rules even if it never calls a Stringwork tool.
- Conflict order. The first file wins. Later files refine; they do
not contradict. This is the same model swarm-forge uses for prompt
stacking and the same model Cursor uses for
.cursor/rules.
A constitution lives on disk as a directory of markdown files. The
default location is ~/.config/stringwork/constitution/.
~/.config/stringwork/constitution/
├── constitution.md # Optional entry point. Documents reading order.
├── engineering.md # Example: language-agnostic engineering rules.
├── security.md # Example: secrets / private data rules.
└── reviews.md # Example: PR review checklist (scope: review).
constitution.md is special only in one way: if it contains a markdown
ordered list, that list defines the order in which the other files are
read. Files not in the list appear after the listed files, in
alphabetical order. If constitution.md is missing, every .md file in
the directory is included alphabetically.
Every other file is just markdown. Keep them small and focused — the contents are inlined into every prompt, so length matters.
Manage the constitution from the command line; no daemon required.
# Scaffold a starter constitution at ~/.config/stringwork/constitution/.
mcp-stringwork constitution init
# Print the resolved preamble (the same text injected into MCP responses).
mcp-stringwork constitution show
# Print the inlined version (the same text injected into spawn prompts).
mcp-stringwork constitution show --inline
# Preview what a review-flavoured task would receive.
mcp-stringwork constitution show --task-kind review
# Pull every configured 'git' source into its cache. No-op for 'dir'
# sources. Run this whenever a team source publishes a new ref.
mcp-stringwork constitution sync
# Validate every source: directory exists, files match include globs,
# git remotes respond. Exits non-zero on any ERROR; missing
# global directory is a WARN with a hint to run `init`.
mcp-stringwork constitution doctorEvery constitution is a stack of sources. Each source is a place to read markdown files from. The resolver concatenates all sources in declaration order, applies scope filters, and returns the final file list.
Every Stringwork install gets one source for free, named global,
pointing at ~/.config/stringwork/constitution/. This is the per-user
constitution; running constitution init populates it.
Add more sources in config.yaml:
constitution:
sources:
# A second local directory (e.g. a personal cheatsheet).
- name: my-rules
type: dir
path: ~/work/personal-rules
include: ["*.md"]
# A team-wide rule pack pulled from a git remote.
- name: team-rules
type: git
repo: git@github.com:my-org/team-rules.git
ref: main
paths: ["rules", "instructions"]
cache_dir: ~/.cache/stringwork/team-rules
scope:
task_kind: ["review"] # only attached to review-style tasksdir sources are read live from the filesystem. git sources are
shallow-cloned into cache_dir and refreshed via mcp-stringwork constitution sync — never on the hot path of claim_next.
Path tokens supported in path, cache_dir, and paths:
~/~/...— user home directory.$VAR/${VAR}— environment variables.$PROFILE_DIR— only inside team profile files; resolves to the directory of the profile file itself.
Sources can be scoped so they only attach to certain tasks.
- name: review-checklists
type: dir
path: ~/.config/stringwork/review-rules
scope:
task_kind: ["review"]
agent_roles: ["reviewer"] # optional, matches AgentInstance.RoleThe task_kind field is derived from a task's Template (set by the
task-templates planner) and Title, via the
constitution.TaskKindForTask alias rule:
- Tasks carrying a known template id map directly to that
template's aliased kind. Today there is one alias —
Template = "code-review"→ kind"review". New aliases land alongside new templates in the same change, intemplateKindAliases(internal/constitution/constitution.go). - Tasks with no template (the empty-string case, including all
pending rows that pre-date Phase 2) fall back to the legacy
TaskKindFromTitleheuristic — currently a case-insensitive substring check for "review". This protects pre-deploy tasks: they keep matching review-scoped sources exactly as they did before theTemplatecolumn existed. - Tasks with an unknown template id return no kind, even if the title would match the heuristic. The explicit template wins. This stops a future "bug-investigation: review the logs" task from accidentally pulling in review checklists.
The only kind the system emits today is "review". The
KnownTaskKinds slice and the mcp-stringwork constitution doctor
command both close over that set, so a profile scope writing
task_kind: ["code-review"] (the template id) gets a doctor warning
with an alias hint pointing at the right kind to use ("review").
Aspect-scoped rules are not supported. A
task_kindfilter only matches the alias kind ("review"), never an aspect-scoped string like"review:security"or a template id like"code-review". Aspect-specific guidance lives in the task description composed bytask_plan(see TASK_TEMPLATES.md) — the constitution attaches the same rules to every aspect of a planned task.
Empty scope filters mean "always match".
When a team already has a devtools repo (rules, style guides, review
checklists), they can publish a profile alongside it instead of
asking every developer to maintain their own constitution.sources
block. See TEAM_RULES.md for the full team workflow,
including the example
regfin-devtools.profile.yaml.
flowchart LR
Cfg["config.yaml"] --> Pol["Policy.ConstitutionSources"]
Global["global<br/>~/.config/stringwork/constitution"] --> Pol
Profile["profile.sources<br/>(team devtools)"] --> Pol
User["constitution.sources<br/>(user-declared)"] --> Pol
Pol --> Resolve["constitution.Resolve(scope)"]
Resolve --> Preamble["BuildPreamble<br/>(claim_next, get_work_context)"]
Resolve --> Inline["BuildInline<br/>(buildTaskPrompt)"]
Source order in the resolver is always:
global(built-in user-level directory).- Profile sources, if
constitution.profileis set. - User-declared
constitution.sources.
That order is also the precedence order: global wins ties against
profile sources, profile sources win against user-declared sources. In
practice you rarely hit the tie-breaker — files have unique paths — but
the rule is fixed so behaviour is predictable when it does happen.
- No daemon required.
init,show,sync, anddoctorall read config and the filesystem directly. They work whether or not the server is running. - No network on the hot path. Resolution never touches the network.
Only
constitution syncdoes, and only when invoked explicitly. - Bad declarations are skipped. A typo in one source entry logs to stderr and the rest of the chain continues to resolve.
- Empty constitution. If nothing resolves (no sources configured, empty directories), the preamble and inline output are empty strings — workers see no extra header. Spawn prompts continue to work as before.