| name | flag-parser |
| description | Parses workflow flags (--quality-locked, --autonomous, --subagents) from $ARGUMENTS at the start of /build and /architect commands. Uses a deterministic python3 runtime with a prose-rule fallback if python3 is unavailable. Returns a strict JSON contract that the agent trusts unconditionally.
|
| version | 1.1.0 |
| modes | ["build","architect"] |
| type | process |
Flag Parser
Workflow flags are passed inline in slash command arguments. Both
/build and /architect accept the same flags, parsed identically.
Parsing must be deterministic. Prose-only parsing by the agent is
unreliable — the runtime layers below produce the same JSON output and
the agent trusts that JSON unconditionally.
Supported Flags
| Flag | Effect | Default |
|---|
--quality-locked | Loop review/revise until findings are clean or cap (10) hit | off (overridable by config) |
--no-quality-locked | Force off, overriding a config default | — |
--autonomous | Agent makes elicitation decisions from memory/codebase/principles | off (overridable by config) |
--no-autonomous | Force off, overriding a config default | — |
--subagents | Fresh implementer + reviewer subagent per plan task (ADR-10) | off (overridable by config) |
--no-subagents | Force off, overriding a config default | — |
--subagents is different from the other two, and the difference matters.
The others are policies: ask for them and you get them. This one is a
capability — it requires the platform contract's subagent-dispatch. Where
that is absent, the request is REFUSED, not silently downgraded: the workflow
announces it, the degradation hook writes a decisions.md line, and the
manifest records execution_mode: inline (subagents-unavailable). Call
resolve_execution_mode() in sage_flags.py rather than reading the flag
directly — a bare flag read is how a "fresh reviewer per task" quietly becomes
the same context reviewing itself.
Precedence (highest wins)
For each mode (quality_locked, autonomous):
| Priority | Source | Effect | Source label |
|---|
| 1 (highest) | --no-<flag> | Force off | "flag" |
| 2 | --<flag> | Force on | "flag" |
| 3 | <flag>: true in .sage/config.yaml | Default on | "config" |
| 4 (lowest) | nothing | Off (current behavior) | null |
Flag vs config when they agree: the source label is "flag"
(explicit intent always labels, even when the value matches config).
Functional outcome is the same (mode on).
Conflict: passing --<flag> AND --no-<flag> in the same invocation
is a user error. The parser returns an error JSON and exits non-zero.
Config File Defaults
Read from .sage/config.yaml. Strict-match contract: only lines
matching exactly <key>: true (with one space after the colon,
lowercase true, no trailing characters) are honored. The strict form
ensures Python and the prose rules agree byte-for-byte. Rejected variants
(treated as no default):
quality_locked: True (titlecase)
quality_locked: "true" (quoted)
quality_locked: yes (YAML alias)
quality_locked:true (no space)
quality_locked: true (extra space)
quality_locked: true # comment (trailing content)
- Any nested/indented key (only top-level keys are read)
quality_locked: false is equivalent to no default — value is off.
Use the --quality-locked flag to override.
JSON Contract
Both parsing layers emit the same JSON shape to stdout:
{
"quality_locked": true | false,
"autonomous": true | false,
"subagents": true | false,
"goal": "<remainder after flags, trimmed>",
"error": null | "<error message>",
"quality_locked_source": "flag" | "config" | null,
"autonomous_source": "flag" | "config" | null
}
Source value is "flag" whenever a flag (positive --X or negative
--no-X) influenced the result. "config" only when no flag was
passed AND config provides the default-on. null when the value is
the implicit default-off.
Exit code semantics:
0 — clean parse (error: null)
1 — unknown flag, conflicting flags, or malformed input
(error is populated; JSON still printed)
Parsing Order (Try Each Layer)
The agent tries each layer in order. As soon as one returns valid JSON,
use it and skip the rest.
Layer 1 — Python (primary, preferred)
python3 sage/runtime/tools/sage_flags.py parse "$ARGUMENTS" --config-path .sage/config.yaml
The --config-path is optional — when provided, the parser reads
quality_locked: true / autonomous: true lines as defaults. When
omitted (or the file is missing/malformed), no defaults apply.
Outputs JSON to stdout. Exit 0 on clean parse, 1 on unknown flag or
conflict.
Layer 2 — Prose-rule fallback (last resort)
Use ONLY when Python is unavailable (rare — Sage requires python3, so this
is a locked-down or broken environment). The agent reads the parsing rules
below and produces JSON manually.
Announce when falling back:
Sage: Deterministic parser unavailable (python3 not found).
Using prose-rule fallback for flag parsing.
This is the only case where prose parsing is acceptable.
Parsing Rules (used by both layers)
- Flags must appear before the goal description. Flags at the end
are not parsed — they're treated as part of the goal.
- Flag order doesn't matter.
--autonomous --quality-locked and
--quality-locked --autonomous are equivalent.
- Unknown flags are an error. If $ARGUMENTS starts with
-- and
the flag name isn't recognized, return JSON with error populated.
- No values, just booleans. Neither flag takes an argument.
--quality-locked=true is not supported.
- Goal is everything after the flags. Surrounding whitespace
trimmed; internal whitespace preserved.
Examples
INPUT: "Ship dark mode"
OUTPUT: {"quality_locked": false, "autonomous": false, "goal": "Ship dark mode", "error": null}
INPUT: "--quality-locked Ship dark mode"
OUTPUT: {"quality_locked": true, "autonomous": false, "goal": "Ship dark mode", "error": null}
INPUT: "--autonomous --quality-locked Ship dark mode"
OUTPUT: {"quality_locked": true, "autonomous": true, "goal": "Ship dark mode", "error": null}
INPUT: "--quality-locked"
OUTPUT: {"quality_locked": true, "autonomous": false, "goal": "", "error": null}
INPUT: "Ship --quality-locked dark mode" # flag not at start
OUTPUT: {"quality_locked": false, "autonomous": false, "goal": "Ship --quality-locked dark mode", "error": null}
INPUT: "--foo bar"
OUTPUT: {"quality_locked": false, "autonomous": false, "goal": "", "error": "Unknown flag '--foo'. Supported flags: --quality-locked, --autonomous."}
EXIT: 1
After Parsing
On clean parse (error: null)
- Use
quality_locked and autonomous booleans for the rest of the
workflow.
- Use
goal as the user's task description (may be empty — workflow
will auto-pickup from .sage/work/).
- Announce active modes (if any) before starting work:
Sage → build workflow.
Modes: --quality-locked, --autonomous
Goal: Ship dark mode for the dashboard
If both flags are false, omit the Modes line entirely.
On error (error populated)
Surface the error verbatim to the user and stop the workflow:
Sage: {error message}
Do NOT guess what the user meant. Wait for them to retry with the
correct flag name.
Manifest Persistence
After successful parsing, before starting Step 1, the workflow writes
flag state to manifest.md frontmatter:
flags:
quality_locked: true
autonomous: true
On /continue, the workflow reads these fields and restores both
modes for the duration of the session.
Failure Modes
| Situation | Behavior |
|---|
| Python missing | Fall through to the prose-rule layer (announce degradation) |
| Unknown flag | Surface error message, stop workflow |
| Empty $ARGUMENTS | Both modes off, empty goal — workflow may scan .sage/work/ for active initiative |
/continue overrides | New invocation's flags override manifest; note to user |
Quality Criteria
- Parser is deterministic — same input always produces same output
- Both layers produce IDENTICAL JSON for the same input (verified by parity tests)
- Error messages name the unknown flag and list supported flags
- Goal preserves user-supplied whitespace and casing
- Flag state is announced and persisted before any artifact work begins
- Prose-fallback announcement is mandatory so the user knows reliability is degraded