OKRs
Cascading objectives and key results in Pact — with workstreams, tasks, and key results that sync their progress from live CRM signals.
OKRs
Pact's OKR engine gives your workspace a four-level goal structure — objectives → key results → workstreams → tasks — with two features that make it more than a spreadsheet: objectives cascade (a child objective rolls up to a parent), and key results can auto-sync their current value from live CRM signals instead of being hand-updated.
Live
Objectives, key results, workstreams, and tasks are backed by real tables
(objectives, key_results, workstreams, workstream_tasks — alembic
0078) and the /v1/okrs API in api/routes/okrs.py. Progress and signal
resolution live in core/okrs/.
The four levels
| Level | Table | Purpose |
|---|---|---|
| Objective | objectives | A qualitative goal with an owner, a period (period_start/period_end), and an optional parent_objective_id for cascading. |
| Key result | key_results | A measurable outcome with target_value, starting_value, current_value, unit, and a signal_source. |
| Workstream | workstreams | A body of work under an objective, with its own owner and status. |
| Task | workstream_tasks | A concrete to-do under a workstream, optionally with a due_at. |
Working with objectives
POST /v1/okrs/objectives— create an objective (optionally setparent_objective_idto cascade it under another).PATCH /v1/okrs/objectives/{id}— update title, description, period, or status.GET /v1/okrs/objectives/GET /v1/okrs/objectives/{id}— list or fetch, with caller-scoped visibility.GET /v1/okrs/cascade— the objective tree, resolved throughparent_objective_idedges so a manager can see how their team's objectives ladder up.
Key results, workstreams, and tasks have their own create/patch endpoints
under the same /v1/okrs prefix.
Key results that sync themselves
Every key result declares a signal_source. manual means you update
current_value yourself. The other sources pull a live number from the platform
when you call POST /v1/okrs/key-results/{id}/refresh:
signal_source | Resolves to |
|---|---|
manual | The stored current_value, unchanged. |
pipeline_value | Sum of in-flight opportunities.value for the owner. |
win_rate | Won / total opportunity rate for the owner. |
account_health | Owner's account-health signal. |
sequence_engagement | Sequence engagement for the owner. |
custom_query | A tenant-defined query carried in signal_query_json. |
The value is resolved by core/okrs/signal_resolver.py, and the refresh
(core/okrs/progress_engine.py's refresh_kr) writes the new number and stamps
last_synced_at. An unrecognized signal source is logged and leaves the value
untouched rather than failing the request.
Cascade is validated
core/okrs/cascade_validator.py keeps the objective tree acyclic — an
objective can't become its own ancestor — so roll-ups always terminate.