---
name: "skill-portfolio-allocation"
type: skill
license: CC BY-NC-SA 4.0
description: 'The allocation decide-step for a portfolio operator — how operator-apps decides across its bets instead of driving one goal to a target. Ranks the portfolio''s bets by leverage, allocates the cycle''s finite fleet capacity across them top-down, and recommends a per-bet disposition (sustain, park, restart, kill, graduate). Composed by operator-apps alongside skill-operating-loop, replacing the loop''s single-goal decide step; not a standalone schedule. Read when running the Apps accelerator loop, or when reasoning about how a portfolio''s fleet capacity is shared. Holds the altitude split — this arbitrates across bets within one portfolio, Atlas arbitrates across operators — and the mandate boundaries: GTD off the commercial axis, os.Claude attention not canon authority, kill and graduate as Owner escalations.'
---

# portfolio-allocation

The decide-step for a **portfolio** operator. A single-outcome operator (BARA, A1, bot.Trader, Mr P) decides the next best actions toward *one* goal; a portfolio operator (`operator-apps`, the accelerator) instead decides how to **share finite fleet capacity across several bets**. This skill is that decide-step: it drops into `skill-operating-loop` in place of its single-goal "decide the next best actions" step, and hands its output back to the same dispatch, mandate-edge and report steps. It never executes work and never re-implements Atlas — it arbitrates *across bets within one portfolio*, where Atlas arbitrates *across operators* (`core-operating-model`, Layer 3).

## Trigger

Run by a portfolio operator inside its operating loop (`task-apps-operator-loop` → `skill-operating-loop`), in place of that loop's single-goal decide step. Requires the portfolio's bets, each bet's goal / KPI / health, and the cycle's available fleet capacity. Not a standalone scheduled job.

## Behaviour

### Pass 0 — Reconcile last cycle's allocation against what actually ran

Before ranking anything, read the previous cycle's allocation from this operator's last status update and the allocation table on the Owner-session agenda, and mark each slice **consumed**, **partially consumed**, or **never started**, from the tickets' own state (`completedAt`, `startedAt`, an open PR). Capacity allocated to a lane that cannot execute is not spent — it is wasted and reported as spent, and the starvation call in Pass 3 is then wrong in both directions: a bet is recorded as fed when it was not, and the capacity it nominally consumed was never available to a bet that could have used it. Feed non-consumption into Pass 2: a bet whose slice never started does not rank as if it had been fed. A slice **never started for two consecutive cycles** is a **capability gap**, not a scheduling accident — name it, and escalate it on the loop's mandate-edge path in Pass 5 with a recommendation (re-route the surface, or park the bet), never re-allocate it a third time. `skill-fleet-dispatch`'s readiness test holds the same un-runnable tickets daily (APP-991) and does not reach this allocator; the Figma write-path gap is one cause (APP-963); this pass is the class. (Worked case: the 2026-08-17 loop allocated APP-122 and APP-508 to Pixel on the Figma surface and the Forge lane to os.Claude; the Forge slice was consumed within 24 hours, both Pixel slices were untouched five days later, and only a re-read of the same ticket IDs stopped a third identical allocation — APP-1049.)

### Pass 1 — Resolve membership, then read each bet's standing

Resolve the portfolio's membership from the **tracker** — the vertical's team and its live projects in Linear (`convention-linear`) — not from a list named in prose. Membership is a rule applied to live state, never a list. A bet is a project in the vertical's team that is linked to the vertical's initiative, less the standing named exceptions the operator profile records (for Apps, `operator-apps` *Mandate*). Projects that share a bet-level initiative are read as one bet. Each bet's **active or inactive** standing is read from its project status on the same run. In Progress or Planned is active. Hold, Backlog, Paused, Completed or Canceled is inactive, and an inactive bet gets no allocation, no date escalation and no cycle proposal. Read the status **name**, not its type: Hold is type `planned` in this workspace (`convention-linear`), so a read by type counts a parked bet as live. Canon holds the rule and Linear holds the state. So a park, restart, kill or graduate ruling is applied to the project in Linear and logged in the decision log, and is never written back into canon as a list (Owner, 2026-09-21 — APP-1578). The first run under this rule names every bet whose standing it changes from the last report. Any project in the team that the rule neither includes nor excludes is a **membership-drift** item, and so is one linked to no initiative of the vertical's that is not a named exception: name it explicitly. Because membership drift is a **goal-set change**, surface each drift item to the Owner on the loop's mandate-edge escalation — the escalation shape already exists and ran by hand in APP-882; resolving from the tracker makes it fire by design rather than by chance. Without the diff a new project created in the vertical's team is invisible to the operating loop until someone re-reads the mandate, and a missed drift also suppresses the Owner decision it should have triggered, so the diff is not optional (APP-887).

**A project the rule *includes* is a bet by that act — its first appearance is not drift, and not a goal-set change.** The sentence above makes drift a goal-set change, and drift is defined narrowly: a project the rule *neither includes nor excludes*. A project the rule **does** include — in the vertical's team, linked to the vertical's initiative, not a named exception — is settled by the act of linking it, and needs no separate Owner ruling to become a bet. Name it in the run's report as a new bet with its standing, and allocate to it. Escalate only where (a) the rule cannot place it, (b) a named exception is in question, or (c) the run **recommends a non-default placement** — that recommendation is the Owner's call, the membership is not. Without this the two readings compose into an Urgent Owner-lane question the operator already had the answer to: app.DS-Quality was raised on 2026-09-22 as *is this a sixth Apps bet*, sat two weeks, and was closed on 2026-10-06 with the Owner pointing out the question was already answered by the live-resolution rule the ticket itself cited (APP-1639, APP-2118; same shape previously in APP-882, APP-995 and APP-1325, each of which was a genuine exception call and so correctly escalated).

For every resolved bet, read its goal, KPI movement, health, in-flight work, and blockers from its own sources. A bet with **no outcome goal** is named as a strategy gap to close, not scored as on-track. Read the real numbers, never assumed ones.

The principle — resolve scope from the tracker, never from a list in prose — recurs across artefacts; the Owner has already ruled it for `task-daily-report` (APP-876 decision 2), so the read pattern and the escalate-the-diff behaviour are phrased to match. `skill-operating-loop` step 1 carries the same exposure at lower volume for a single-outcome operator; note it there, but it is not fixed by this edit.

### Pass 2 — Rank the bets by leverage

Rank the bets by expected outcome movement per unit of fleet capacity this cycle, weighted by strategic priority (the Priority Board / latest `skill-plan` focus) and by genuine near-term commitments — a live launch, or a target date that is a real commitment (never one sitting behind a Hold, `convention-linear`). Name the ranking and the one-line reason for each position.

### Pass 3 — Allocate finite fleet capacity

Allocate the cycle's **available** fleet capacity (one Forge per repo, the connector fleet's bandwidth) top-down across the ranked bets until capacity is spent. Name explicitly which bets are **starved** this cycle rather than pretending everything fits — finite capacity is the whole point of the exercise. **Allocate only to a ticket with no open** `blocked-by`**.** Read each candidate's relations before it takes a slice. A ticket behind an open blocker cannot consume capacity this cycle, so allocating to it books Pass 0's never-started slice in advance. A bet whose ready set is entirely blocked is reported as **starved by precondition**, naming the blocker and its lane, and the capacity passes down the ranking. (Worked case: the 2026-09-14 loop gave Luna's Forge slice to APP-60, APP-61 and APP-63. Each had carried `blocked-by` APP-1034, a `human` credential ticket, since 2026-09-13. None started, and the report guessed "may gate on Vercel" when the relation was on the ticket — APP-1571, 2026-09-21.) **GTD** is allocated on personal-infrastructure grounds, never the commercial axis; **os.Claude** is allocated *attention* only, never authority over canon (which still moves Pulse-proposes → Owner-saves).

### Pass 4 — Per-bet disposition

For each bet, recommend exactly one:

- **Sustain** — keep allocating; it is earning its capacity.
- **Park** — stop allocating and move the project to Hold; revisit later. (Operator's call within mandate.)
- **Restart** — un-park a Held bet back into allocation. (Operator's call.)
- **Kill** — retire the bet. A **goal-set change → Owner escalation**, never unilateral. GTD is exempt from kill-for-no-revenue.
- **Graduate** — the bet has earned its own operator; a peer vertical leaves the portfolio. An **Owner call**, case by case, no written bar (`core-operating-model`, *Graduation*).

### Pass 5 — Hand back to the loop

Return the ranked allocation and the per-bet dispositions as the decide-step's output. `skill-operating-loop` runs its dispatch (via Relay), mandate-edge check, autonomous-proceed and report steps on it unchanged. **Kill** and **graduate** recommendations ride the loop's mandate-edge escalation to the Owner; park / restart / sustain proceed within mandate. Membership-drift items raised in Pass 1 ride the same escalation.

## Guardrails

- **Allocation, not execution.** Proposes how capacity is shared; the fleet executes via Relay. Never runs the work itself.
- **Membership comes from the tracker, not prose.** Resolve the portfolio's bets, and each one's active standing, from the vertical's Linear team, initiative and project status, less the named exceptions. Escalate whatever the rule cannot place. Canon holds no list of bets to diff against (APP-887, APP-1578).
- **Finite capacity is the point.** Never allocate beyond the cycle's real fleet capacity; name what is starved.
- **The altitude split.** Arbitrates across bets within one portfolio; Atlas arbitrates across operators. Never re-implement Atlas.
- **GTD off the commercial axis; os.Claude attention, not authority.** Never judge GTD on revenue; never grant the operator canon authority over os.Claude.
- **Kill and graduate are Owner escalations**, never unilateral; park / restart are the operator's within mandate.
- **Read real KPI / health, no fabrication** (`convention-ticket`).

## Setup

Composed by `operator-apps` alongside `skill-operating-loop` — it is the loop's decide-step for a portfolio, not a separate schedule. Reusable if a second portfolio operator ever appears. On finish, run `skill-ops-retro` capture on this run's friction: file a `FRICTION:` note to the Pulse queue for the drain to assess.

## Tone

Calm, precise, sentence case, British spelling, no exclamation marks, outcome before adjective. (cos.tov.)
