# Flag Parser

> 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.

- Skill: `xoai/flag-parser` (Agent Skill)
- Install (CLI): `npx skillmds@latest add xoai/flag-parser`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xoai/flag-parser/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: xoai (https://skillmd.com/u/xoai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/xoai/flag-parser

---


# 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 | — |
| `--parallel[=N]` | Dependency-aware parallel lanes on top of `--subagents` (A7); the one value-taking flag — N is the lane cap, default 2, hard cap 4 (over-asking clamps loudly into `parallel_note`) | off (overridable by config) |
| `--no-parallel` | 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, subagents — the ladder is the
same for all three; subagents' extra step, platform refusal, happens
AFTER parsing, R97):

| 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:

```json
{
  "quality_locked": true | false,
  "autonomous": true | false,
  "subagents": true | false,
  "parallel": true | false,
  "goal": "<remainder after flags, trimmed>",
  "error": null | "<error message>",
  "quality_locked_source": "flag" | "config" | null,
  "autonomous_source": "flag" | "config" | null,
  "subagents_source": "flag" | "config" | null,
  "parallel_source": "flag" | "config" | null,
  "parallel_lanes": 0 | 1-4,
  "parallel_note": null | "<clamp note>"
}
```

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)

```bash
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)

1. **Flags must appear before the goal description.** Flags at the end
   are not parsed — they're treated as part of the goal.
2. **Flag order doesn't matter.** `--autonomous --quality-locked` and
   `--quality-locked --autonomous` are equivalent.
3. **Unknown flags are an error.** If $ARGUMENTS starts with `--` and
   the flag name isn't recognized, return JSON with `error` populated.
4. **No values, just booleans.** Neither flag takes an argument.
   `--quality-locked=true` is not supported.
5. **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)

1. Use `quality_locked` and `autonomous` booleans for the rest of the
   workflow.
2. Use `goal` as the user's task description (may be empty — workflow
   will auto-pickup from `.sage/work/`).
3. 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:

```yaml
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

