Skip to content

Envelope Tools

All envelope tools require Manager or Owner role unless noted. The four envelope writes below (allocate_hours, update_allocation, cancel_allocation, reactivate_allocation) are performed in the web console rather than asked for in Slack; the reads on this page remain Slack-reachable. See Envelopes for conceptual background.


allocate_hours

Create a per-person hour envelope on a project.

Console-only — not reachable from Slack; use the web console.

Writes to: 07. Budgets

Parameters:

ParamRequiredDescription
requesting_user_slack_id
project_name
user_name or user_slack_idPerson receiving the envelope
allocated_hoursHours for this envelope
task_id or task_nameWork-category tag — must be enabled for the project
start_dateEnvelope window start, YYYY-MM-DD.
end_dateEnvelope window end, YYYY-MM-DD.
labelFree-text wayfinder (max 40 characters), e.g. "Location A".

Task constraint: The task must be enabled for the target project (via Edit tasks / enable_task_for_project). A project with no enabled tasks cannot receive any envelope. A task that is globally active but not enabled for the project is rejected with a 400 error listing which tasks are enabled.

Window (optional, storage only): start_date/end_date bound when an envelope's hours are expected to land — a bound, not a schedule. Both absent/empty (the default) means project-lifetime, exactly today's behavior. Either supplied requires both — a one-sided window is rejected (❌ Failed: start_date and end_date must both be provided, or both omitted.). end_date must be on or after start_date (same-day is a valid one-day window). Set only from the web console's Add-envelope form; this pair is deliberately absent from the Slack tool schema, so VERA can never set or infer one. As of this write, the window changes no calculation anywhere — proration, capacity, and availability are unaffected.

Label (optional, storage only): label lets a manager or owner tell apart several envelopes belonging to the same person on the same project — e.g. two "Photography" envelopes on one project, one for Location A and one for Location B, where the task alone doesn't say what each one is for. Plain free text, trimmed, up to 40 characters (❌ Failed: label must be 40 characters or fewer. beyond that); blank or whitespace-only stores as no label. It is display-only — it changes no calculation anywhere. (Separately, a time entry logged from the web console can be attributed to one specific envelope of a same-task group — see allocation_id on log_time and the bucket's per-envelope hours_logged above — but a label never drives that attribution on its own.) It is deliberately never shown in the Slack morning briefing. Set and shown only in the web console (Add-envelope form, the standalone envelope page, and every envelope table); this parameter is deliberately absent from the Slack tool schema, so VERA can never set, read, or mention it.

Returns: envelope id (use for updates/cancellations), and overbooked: true if the person is over capacity.

If overbooked: true is returned, always surface the warning: "⚠️ [Name] is overbooked by Xh."

Both word orders accepted: allocate 20h to Apex for Rachel as Design (natural: to <project> for <person>) and allocate 20h to Rachel on Apex for Design (legacy: to <person> on <project>) resolve identically. VERA binds each name to the slot matching what it actually is — no re-asking when all slots are present.


update_allocation

Change the hours or task on an existing envelope.

Console-only — not reachable from Slack; use the web console.

Updates: 07. Budgets col D (hours), col I (task)

Parameters:

ParamRequiredDescription
requesting_user_slack_id
allocation_idThe envelope's ID
new_allocated_hoursNew hours value
new_task_id or new_task_nameNew task category
start_dateNew envelope window start, YYYY-MM-DD. Presence-based (see below).
end_dateNew envelope window end, YYYY-MM-DD. Presence-based (see below).
week_hoursNew shape: array of { week, hours }. Presence-based (see below).
labelNew free-text wayfinder (max 40 characters). Presence-based (see below).

To find the envelope ID, call get_allocations or get_person_allocation_summary first.

Restrictions: Cannot target a Retainer or Agile parent project — envelopes must reference the active period or sprint child. The error message names the active period to guide the user.

Audit: The audit message shows exactly what changed (e.g. "40h → 50h" or "task: Design → Development").

Window (optional, storage only, presence-based): start_date/end_date follow the same both-or-neither and ordering rules as allocate_hours, but the update path is presence-based rather than value-based: omit both keys entirely to leave the existing window untouched; send both as non-empty values to set/replace it; send both as empty strings to clear it back to null; send only one (present or filled) and the call is rejected with the same ❌ Failed: start_date and end_date must both be provided, or both omitted. message. As with allocate_hours, this pair is console-only and changes no calculation.

Shape (optional, storage only, presence-based — issue #1810): week_hours is an array of { week, hours } describing how an envelope's hours land across the weeks of its window — a click-to-draw bar chart in the console's Edit-envelope modal, on /console/p/:projectId and /console/envelopes. Presence-based like the window pair: the key absent leaves the existing shape untouched; [] or null clears it; a non-empty array is validated and written. Requires a window — week_hours is rejected with ❌ Failed: this envelope has no window — set a window first… if start_date/end_date are both null (after this same request's own window change, if any). Every week must be the Monday of a week inside the window; every hours must be a number greater than 0 (a week with no hours is simply omitted, not stored as zero); no duplicate weeks; the sum of hours must not exceed the envelope's allocated_hours (checked server-side regardless of what the console's own client-side clamp already enforced). Stored sorted by week. This pair is console-only — absent from the Slack tool schema, same as the window — and changes no calculation. Critically: the shape is a forecast the PM owns, never an instruction handed to the person whose hours they are — no alert, chip, color, or signal anywhere may derive from comparing logged time against this shape, now or in any future issue.

Deterministic routing (M-ENV-U): Natural language like "update Sarah's envelope on Acme to 50h" resolves the target envelope directly from the entity pipeline. If the envelope resolves unambiguously and new hours are present, VERA shows a before→after confirm gate ("Change … (40h) to 50h? Reply 1 to confirm.") — no model round-trip. If the user holds two envelopes on the same project (e.g. Design + Development), VERA shows a numbered disambiguation menu; the model never picks the candidate.


cancel_allocation

Mark an envelope as cancelled. The record is kept for audit purposes. Cancellation is terminal — VERA does not track lineage between a cancelled envelope and any replacement.

Console-only — not reachable from Slack; use the web console.

Updates: 07. Budgets col F (status → "cancelled")

Parameters: requesting_user_slack_id, allocation_id

Deterministic routing (M-ENV-D): Natural language like "cancel Sarah's envelope on Acme" resolves and always shows a terminal confirm gate ("Cancel … ? This is terminal — reply 1 to confirm.") before executing. Ambiguous → disambiguation menu. The model never cancels without explicit user confirmation.


reactivate_allocation

Restore a cancelled envelope to active. Use when an envelope was cancelled by mistake.

Console-only — not reachable from Slack; use the web console.

Updates: 07. Budgets col F (status → "active")

Parameters: requesting_user_slack_id, allocation_id

No uniqueness check is performed — multiple active envelopes for the same user/project/task are allowed.

Audit: ♻️

Deterministic routing (M-ENV-D undo): Natural language like "reactivate Sarah's envelope on Acme" fires instantly when the cancelled envelope resolves unambiguously — no confirm gate (non-destructive). Ambiguous → disambiguation menu; the digit fires the reactivation directly. No model invocation.


get_allocations

List envelopes with optional filters.

Reads from: 07. Budgets, 03. Users, 02. Projects, 05. Tasks

Parameters:

ParamDescription
requesting_user_slack_id
user_idFilter by person
project_idFilter by project
status"active" (default) or "cancelled"
include_usage"true" to add usage enrichment (see below). Default omits it.

Returns: Each allocation includes user_name, project_name, and task_name (the resolved task label, or null if no task is tagged).

When include_usage=true, envelopes are bucketed by (user_id, project_id, task_id) before enrichment. Multiple envelopes sharing the same triple collapse into one bucket row; allocated_hours is summed and time entries are attributed once — no double-counting. Each bucket row includes:

FieldDescription
allocation_idThe single envelope's id if the bucket has exactly one envelope; null for multi-envelope buckets
envelope_idsArray of all envelope IDs in this bucket
envelopesArray of constituent envelope objects { allocation_id, allocated_hours, hours_logged, ... } — each envelope's own hours_logged is the hours from entries whose budget_id names that exact envelope; used for per-envelope Edit/Cancel modals and, as of issue #2047, for the console's per-envelope Logged/Remaining display
display_project_id / display_project_nameThe parent project for retainer/agile period children, otherwise the project itself
project_deadlineThe period's period_end, or the project's deadline, or the parent's deadline
hours_loggedTotal hours attributed to this bucket (partitioned across buckets, not duplicated) — equals the sum of every constituent envelope's own hours_logged plus hours_unattributed
hours_unattributedHours matching this bucket that belong to no single envelope — a time entry with no budget_id (e.g. logged from Slack, which never names one) or one naming an envelope outside this bucket
budget_remainingallocated_hours − hours_logged (not floored — negative values are meaningful)
pct_usedround(hours_logged / allocated_hours × 100), or null if allocated_hours is 0 and hours have been logged
project_statusThe status (active, cancelled, completed, etc.) of the display project (parent for retainer/agile periods, otherwise the project itself)

Used by the web console's top-level Envelopes overview (/console/envelopes) and the project detail Allocations card (/console/p/:id). As of issue #2047, both render each envelope's own Logged/Remaining rather than the bucket-wide figure, plus a separate row for a group's hours_unattributed (labelled "Unattributed") when it is nonzero.


get_my_allocations

The requesting user's active envelopes.

Parameters: requesting_user_slack_id


get_my_assignments

"What should I work on?" — returns active envelopes for the requesting user with remaining hours.

This is the primary tool for Users to understand their current workload. It shows:

  • Projects they're allocated to
  • Hours allocated vs. hours logged
  • Hours remaining in each envelope

Parameters: requesting_user_slack_id

Any active user. Assignment = having an active envelope. Anyone can log time to any project regardless of assignment — envelopes are a planning tool, not a gate.


get_project_assignments

Who is allocated to a project. Manager/Owner.

Reads from: 07. Budgets, 03. Users

Returns: user_id, user_name, allocated_hours, hours_logged, hours_remaining, task_id (if tagged), status

Permission: Owners see all. Managers see only their assigned projects.


get_person_allocation_summary

All envelopes for one person across all their projects.

Reads from: 07. Budgets, 02. Projects

Parameters: requesting_user_slack_id, user_name

Returns per envelope: project_name, allocated_hours, hours_logged, hours_remaining, status, overdraw flag


get_project_allocation_summary

All envelopes for one project, bucketed by (user_id, project_id, task_id).

Reads from: 07. Budgets, 03. Users, 01. Time Entries, 05. Tasks

Parameters: requesting_user_slack_id, project_name

Multiple envelopes for the same person+task collapse into one row (see bucket model above). hours_logged is attributed once per entry — no double-counting when a person has two envelopes on the same task.


get_team_availability

Remaining hours per person across all active envelopes.

Reads from: 07. Budgets, 01. Time Entries, 03. Users

Returns per person: available_hours, allocated_hours, remaining_hours, overbooked flag

Use this for a quick capacity snapshot before adding new work.


get_estimates_vs_actuals

Compare allocated hours (the plan) against logged hours (the actuals).

Reads from: 07. Budgets, 01. Time Entries

Parameters:

ParamDescription
requesting_user_slack_id
week_start"this week", "last week", or YYYY-MM-DD
user_nameOptional — filter to one person
project_nameOptional — filter to one project

Returns: per person/project: allocated_hours, actual_hours, delta

  • delta > 0 = over plan (more hours worked than allocated)
  • delta < 0 = under plan (fewer hours than allocated)

Surface meaningful divergences (delta ≥ ±20%) as insights. Never show estimates vs actuals for future weeks — redirect to get_allocations instead.


check_capacity

Can the team absorb new work? Takes discipline requirements and a timeline.

Parameters:

ParamDescription
requesting_user_slack_id
discipline_requirementsArray of {discipline, hours_needed}
start_dateYYYY-MM-DD
end_dateYYYY-MM-DD

Returns per discipline:

  • fte_sufficient: FTE has headroom — names and available hours
  • fte_partial: FTE bench partially covers — gap amount
  • contractor_required: No FTE capacity — hours of contractor coverage needed
  • Contractor conflicts flagged if any contractors are double-booked

get_capacity_forecast

N-week rolling capacity outlook.

Parameters:

ParamDescription
requesting_user_slack_id
weeksNumber of weeks to forecast (default 4, max 12)

Returns per week:

  • week_start, utilization_percent, unallocated_hours
  • uncovered_projects: projects with no one allocated that week
  • overbooked users: name + hours over

Interpretation:

  • < 70% utilization → plenty of headroom
  • 70–90% → healthy
  • 90% → nearly full, be careful

  • uncovered_projects → flag immediately — no coverage that week