@hcflabs/pi-task-models9.3.1
Shared task model profiles and routing for hcflabs Pi extensions.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
@hcflabs/pi-task-models
Configure shared Task Profiles for fast, balanced, frontier, and fav. Each Task Route pairs a model with a thinking level; each consumer owns its Model Tasks and defaults.
Install
pi install npm:@hcflabs/pi-task-models
Works with
| Package | Relationship | Purpose |
|---|---|---|
@hcflabs/pi-auto-compact |
Consumer | Compaction defaults to fast. |
@hcflabs/pi-herdr-tools |
Consumer | Side-thread and rename tasks default to fast. |
@hcflabs/pi-memory |
Consumer | Candidate review, /remember preparation, and /dream promotion default to balanced. |
@hcflabs/pi-prompt-creator |
Consumer | Prompt drafting defaults to fast. |
@hcflabs/pi-subagent |
Consumer | Delegation defaults to fast. Callers can declare their own task. |
Use
Run /task-models, select fast, then choose a primary model and thinking level. Choose a different fallback model and thinking level, or None. Reopen /task-models: fast shows the saved route instead of not configured.
| Surface | Type | Purpose |
|---|---|---|
/task-models |
command | For people: configure profiles and assign them to active tasks. |
/task-models preset |
command | For people: apply a saved profile setup. |
/task-models status |
command | For people: inspect current task routes, unavailable slots, and config issues. |
Option+S / Alt+S in /task-models |
shortcut | For people: save configured profiles as a named preset in the interactive terminal. |
| Session start | ui | For people: warn about missing config and config or preset problems. Show an error when routing is disabled. |
task/fast, task/balanced, task/frontier, task/fav |
model | For people and agents: select a Task Profile Model. It fails over from the primary route to the fallback route. |
Configure each profile your consumers require. fav has no fallback. Select an active task to override its declared profile; choosing its declared default removes the override.
Status
Run /task-models status to inspect each active Model Task with the current config and session scope. The command does not execute routes or change files.
Each task line contains the task name and ID, primary model and thinking level, fallback route or none, and issues. Unavailable primary and fallback slots remain visible. Route errors contain the TaskRouteError code and message. Config and preset issues appear on every status request, including issues reported earlier in the session.
With UI, each notification uses one line and the current theme. The notification truncates text to the terminal width. Without UI, stdout contains the complete lines. The command does not display prompts, messages, reviews, or model output.
Task Profile Models
A Task Profile Model is a Pi virtual model, task/<profile>, for one Task Profile. Select it with /model or --model task/frontier. Each request uses a route of the profile: first the primary, then the fallback.
- Pi gets a retryable error on a route: 429, 5xx, overloaded, a network error, or a timeout.
- Pi retries the request. The Task Profile Model then uses the next route of the profile.
- The failed route gets a cooldown of 10 minutes in this process. New turns use the other route during the cooldown.
A tool continuation keeps the route of the previous response, so the prompt cache stays valid. A retry after a context overflow keeps the same route. Profile thinking is authoritative: the selected thinking level does not change the route.
Pi retries only the errors that it classifies as retryable. A 400, 401, 404, or quota error ends the turn on the failed route. Pi waits between retries and uses retry.maxRetries from Pi settings. A Task Route cannot name a virtual model, for example a Task Profile Model or an alias/<name> model. The picker does not show virtual models, and the router skips a Task Route that names one.
@hcflabs/pi-subagent gives a worker the Task Profile Model when its profile has two usable routes. The worker loads only extensions/task-profile-models.ts, because workers run with --no-extensions.
Presets
Run /task-models preset and choose a name. Included profiles replace complete routes, thinking levels, and fallbacks in one atomic update. Omitted profiles and task assignments stay unchanged. Cancellation writes nothing.
To save, open /task-models, press Option+S (macOS) or Alt+S, then enter a name. Configure at least one profile first. The preset contains all configured profiles, not task assignments. Replacing an existing name requires confirmation; other presets stay unchanged. Cancelling either prompt writes nothing.
The shortcut requires an interactive terminal that sends Option/Alt as Meta (Escape); RPC does not support it.
Flow
The menu shows effective profiles for active tasks. Disabled consumers' assignments remain stored but hidden.
Menus and resolution use ctx.scopedModels, including pinned thinking levels. An empty scope uses available text models from Pi's registry. Duplicate numbered Codex aliases collapse into one choice; fallback choices exclude the primary.
Config
Package-owned: ~/.pi/agent/config/pi-task-models/config.json. Only explicit /task-models actions save it. Use exact model IDs from Pi's registry, not these placeholders:
{
"profiles": {
"fast": {
"primary": { "model": "<provider>/<model>", "thinkingLevel": "low" }
}
},
"tasks": {}
}
| Name | Description | Values | Default |
|---|---|---|---|
profiles |
Stores shared routes. | Object keyed by fast, balanced, frontier, or fav. |
{} |
profiles.<profile>.primary.model |
Required primary model. | Canonical provider/model without whitespace or NUL. |
— |
profiles.<profile>.primary.thinkingLevel |
Required primary thinking level. | off, minimal, low, medium, high, xhigh, or max; model support required at resolution. |
— |
profiles.<profile>.fallback |
Route to try when the primary is unavailable or fails with a retryable error. | Object requiring model and thinkingLevel, with primary rules. Forbidden for fav. |
No fallback. |
tasks |
Explicit profile overrides. | Object mapping <package>/<task> IDs to profiles. |
{} |
tasks.<taskId> |
Overrides the declared profile. | fast, balanced, frontier, or fav. |
Task declaration's defaultProfile. |
Task defaults belong to consumer declarations. Existing overrides equal to those defaults remain valid. Numbered openai-codex-N aliases resolve through Pi's registry and save as openai-codex/<model>.
Preset file
You can edit ~/.pi/agent/config/pi-task-models/presets.json manually. It maps names to any subset of the four profiles, using the same route format:
{
"my-setup": {
"fast": {
"primary": { "model": "<provider>/<model>", "thinkingLevel": "low" }
}
}
}
Replace placeholders with exact references from /task-models. Omit fallback to remove it; omit the whole profile to keep its routes. Names must be non-empty, without surrounding whitespace or control characters. The picker sorts names alphabetically.
The file must be valid UTF-8 JSON, at most 64 KiB. Missing, empty, or invalid files do not change active config. Invalid names, presets, and profiles produce warnings and are ignored; valid entries remain available.
Saving refuses any preset-file problem and preserves that file. It reads the latest active profiles, including concurrent changes, before saving. It creates a missing preset file with a private atomic write. An open confirmation prompt does not block other sessions. Saves wait briefly for another save. If another session saves the same new name first, the save reports the conflict and writes nothing; try again.
Applying a preset can create missing active config. Config problems require confirmation before replacement. Applying does not check authentication, availability, or thinking support; task execution uses normal scope and resolution rules.
API
| Surface | Type | Purpose |
|---|---|---|
PROFILE_NAMES |
constant | Lists fast, balanced, frontier, and fav. |
THINKING_LEVELS |
constant | Lists thinking levels. |
ProfileName |
type | Shared profile name. |
ThinkingLevel |
type | Thinking level name. |
ModelTask |
type | Consumer-owned independent model operation. |
TaskModelRoute |
type | Model reference and thinking level. |
TaskModelProfile |
type | Primary route and optional fallback. |
TaskModelsConfig |
type | Profiles and explicit assignments. |
AvailableModel |
type | Pi registry model. |
ResolvedTaskRoute |
type | Session-resolved model and thinking level. |
TaskRouteErrorCode |
type | Route failure code. |
TaskRouteError |
type | Error with code and optional profile name. |
TaskRouteAttempt |
type | Frozen record for one executed route, with model, outcome, duration, and an optional bounded error code. |
registerModelTask(pi, task) |
function | Registers a consumer declaration at extension load. |
loadTaskModelsConfig(agentDir?) |
function | Reads and validates config; throws when disabled. |
registerTaskModelsConfigReporting(pi, agentDir?) |
function | Reports problems at session start; returns report(ctx) for later problems. |
canonicalModelReference(model) |
function | Validates and canonicalizes provider/model, including numbered Codex aliases. |
modelReference(model) |
function | Formats provider/model. |
dedupeAvailableModels(models, preferredProvider?) |
function | Removes duplicate canonical references, with optional provider preference. |
resolveAvailableModel(models, reference, preferredProvider?) |
function | Finds a reference, including numbered Codex aliases. |
availableTaskModels(ctx) |
function | Lists scoped text models, or registry models when scope is empty. |
taskThinkingLevels(ctx, model) |
function | Lists supported levels, respecting session-pinned thinking. |
resolveTaskModelRoute(ctx, route, thinking?) |
function | Resolves one available route. |
resolveConfiguredTaskRoute(ctx, task, agentDir?, thinking?) |
function | Resolves the first usable task route. |
resolveConfiguredTaskRoutes(ctx, task, agentDir?, thinking?) |
function | Resolves configured task route candidates. |
executeTaskRoutes(routes, attempt, { shouldFallback, signal?, onAttempt? }) |
function | Tries resolved routes in order. Returns first success and can report each executed route. |
orderedProfileRoutes(profile) |
function | Returns primary, then optional fallback. |
createTaskModelsExtension(pi, options?) (also default export) |
function | Registers /task-models, missing-config warnings, issue reporting, and the Task Profile Models. |
createTaskProfileModelsExtension(pi, options?) |
function | Registers only the Task Profile Models. extensions/task-profile-models.ts exports it as default. |
TASK_MODEL_PROVIDER |
constant | Provider of the Task Profile Models: task. |
TASK_PROFILE_MODELS_EXTENSION |
constant | Absolute path of extensions/task-profile-models.ts, for a child Pi process. |
TASK_ROUTE_COOLDOWN_MINUTES |
constant | Minutes that a failed route stays skipped: 10. |
taskProfileModelReference(profile) |
function | Returns task/<profile>. |
routeTaskProfile(routes, request, now?, cooldownMinutes?) |
function | Picks the route for one request of a Task Profile Model. |
Model and route helpers use only ctx.model, ctx.scopedModels, and ctx.modelRegistry.getAvailable(). They do not require UI methods, so consumers can use supported Pi releases. Profile thinking is authoritative; resolution uses config.tasks[task.id] ?? task.defaultProfile.
Consumers must not read or write shared config directly. Register registerTaskModelsConfigReporting(pi) in the extension factory and call its report(ctx) before resolution, even without the Task Models extension. Each problem appears once per session across reporters.
loadTaskModelsConfig() returns source: "file" or "missing". Missing config returns { source: "missing", value: { "profiles": {}, "tasks": {} } } without creating files. Disabled-config errors give the path and problems.
executeTaskRoutes() only executes supplied routes. It does not resolve, authenticate, inspect providers, store logs, or retry routes. It rejects empty lists and checks signal before each route. Cancellation or shouldFallback(error) === false stops execution. Allowed failures end with the final error unchanged. Each route call must be atomic and safe to repeat.
The optional onAttempt(record) receives one frozen TaskRouteAttempt after each executed route, including the last route. Routes cancelled before execution produce no record. A record contains no route result, request data, or error message text. The executor never copies error messages or thrown strings into records.
| Field | Meaning |
|---|---|
routeIndex |
Zero-based index in the supplied routes array. |
model |
Actual resolved provider/id, including a numbered Codex provider when selected. |
outcome |
succeeded, failed, or aborted. An aborted signal or an AbortError marks a rejected route as aborted. |
durationMs |
Elapsed route execution time in milliseconds. |
errorCode |
Bounded failure code. Uses error when no accepted code, status, or name exists. |
The executor selects errorCode in this order:
- Use a non-empty string
taskRouteCodeon one line, limited to 64 characters. - Use a non-empty string
error.codeon one line, limited to 64 characters. - Use a finite numeric
error.statusashttp-<status>. - Use
error.nameonly if it matches/^[A-Za-z][A-Za-z0-9_]{0,63}$/. - Use
errorotherwise.
If error metadata access throws, the record uses errorCode: "error". If abort classification fails, the outcome is failed unless the signal is aborted. These failures do not change the route result or fallback decision.
Successful records omit errorCode. The observer can return a promise, but execution does not wait for it. The executor catches and ignores observer failures. Observer failures do not change route results or fallback decisions.
Limits and recovery
Reads never change config. Problems appear at session start and when /task-models opens:
| Problem | Result |
|---|---|
Unknown key, misspelled tasks, unknown profile name, invalid task ID or task profile |
Warn and ignore the entry. Tasks without valid overrides use declared defaults. |
Invalid profile or route; fallback in fav |
Warn and ignore the profile. Tasks using it fail with profile-missing, including the problem. |
Malformed JSON, invalid UTF-8, over 64 KiB, unreadable file, non-object root or profiles, misspelled profiles such as profile |
Disable routing. Every task fails with config-disabled. |
Repair the file manually, or save a profile with /task-models. Problematic config requires confirmation. Saves retain only valid settings, including valid tasks when profiles is invalid. If the file changed while /task-models was open, saving writes nothing; reopen /task-models.
Saving refuses oversized, unreadable, or non-regular files. Fix, shrink, or remove the file first.
Resolution throws TaskRouteError; check taskRouteCode:
| Code | Meaning and recovery |
|---|---|
config-missing |
Config is absent. Run /task-models to create it. |
config-disabled |
Config disables routing; the error gives its path and problems. Repair it or save through /task-models. |
profile-missing |
Profile is absent or invalid; errors identify invalid settings. Configure it in /task-models. |
no-route |
No available route. Choose an available model and thinking level in /task-models. |
Consumers may silence only config-missing, and only with a safe current-session fallback.