Skip to content

feat(workflow): add durable observability contracts - #156

Closed
Waishnav wants to merge 1 commit into
codex/dw-onboardingfrom
codex/observability-contracts
Closed

feat(workflow): add durable observability contracts#156
Waishnav wants to merge 1 commit into
codex/dw-onboardingfrom
codex/observability-contracts

Conversation

@Waishnav

@Waishnav Waishnav commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Workflow runs need a durable, bounded record of what each agent call did and how much model usage it consumed so the CLI and TUI can inspect completed and in-progress work. This bottom layer adds the shared observation and token-usage contracts, an additive SQLite migration, and store operations for ordered call observations plus latest/final usage. Observation payloads are optional and size-capped; providers that do not report a value remain unset.

Summary by CodeRabbit

  • New Features

    • Added workflow agent token-usage tracking, including incremental and final usage metrics.
    • Added structured agent activity and tool observations with timestamps, sequencing, status, provider details, and optional diagnostic data.
    • Added APIs to record and retrieve observations for individual agent calls.
    • Added safeguards to normalize usage values and cap oversized diagnostic payloads.
  • Tests

    • Added coverage for persisted usage metrics, observations, aggregation, and migration updates.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds provider-neutral agent observations and token usage types, migration 9 storage, and WorkflowStore APIs for persisting, aggregating, validating, and retrieving ordered observations.

Changes

Workflow observability

Layer / File(s) Summary
Observation and usage contracts
src/local-agent-observations.ts, src/workflow-types.ts
Adds observation types, tool statuses, token normalization and merging, usage fields on agent calls, observation records, and a 16 KiB diagnostic-data limit.
Observability database schema
src/db/migrations.ts, src/db/schema.ts, src/oauth-store.test.ts
Adds migration 9, usage columns on workflow_agent_calls, and the indexed workflow_agent_observations table.
Workflow store persistence and validation
src/workflow-store.ts, src/workflow-store.test.ts
Persists completion usage and ordered observations, aggregates usage, validates statuses, truncates UTF-8 data, and tests retrieval and stored metrics.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentCaller
  participant WorkflowStore
  participant workflow_agent_calls
  participant workflow_agent_observations
  AgentCaller->>WorkflowStore: completeAgentCall(usage)
  WorkflowStore->>workflow_agent_calls: write usageJson and finalUsageJson
  AgentCaller->>WorkflowStore: appendAgentObservation(observation)
  WorkflowStore->>workflow_agent_observations: insert sequenced observation
  WorkflowStore->>workflow_agent_calls: update aggregated usage
  AgentCaller->>WorkflowStore: listAgentObservations(runId, callIndex)
  WorkflowStore->>workflow_agent_observations: query ordered observations
Loading

Possibly related PRs

  • Waishnav/devspace#83: Introduced the workflow persistence and local-agent execution extended by this change.
  • Waishnav/devspace#95: Added the workflow agent-call lifecycle extended with usage and observation persistence.
  • Waishnav/devspace#96: Introduced the workflow agent-call execution flow extended by this change.

Poem

A rabbit watched each token hop,
Into ordered rows that never stop.
Calls keep totals, tools leave trails,
UTF-8 guards the data scales.
Observations bloom in sequence bright.
Workflow records sleep well tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding durable observability contracts for workflows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/observability-contracts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Waishnav Waishnav changed the title feat(workflow): add observability storage contracts feat(workflow): add durable observability contracts Aug 8, 2026
@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

Adds provider-neutral workflow observability storage contracts and token-usage tracking.

  • Introduces the workflow_agent_observations table and migration.
  • Adds activity and usage observation types, normalization helpers, and store APIs.
  • Persists current and final agent-call usage and extends store coverage.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking issue in how oversized observation JSON is truncated.

The observability schema and usage persistence are coherent, but oversized dataJson payloads are stored as invalid JSON because the new path performs raw byte truncation instead of JSON-aware truncation.

Files Needing Attention: src/workflow-store.ts

Important Files Changed

Filename Overview
src/db/migrations.ts Adds migration 9 with usage columns and the observation table, indexes, and run-level cascade.
src/db/schema.ts Models agent usage fields and the workflow observation storage schema.
src/local-agent-observations.ts Defines provider-neutral activity and token-usage contracts plus normalization and merge helpers.
src/workflow-store.ts Adds observation persistence and usage retrieval, but raw truncation can invalidate oversized dataJson payloads.
src/workflow-types.ts Exposes observation records, token-usage fields, and the observation payload cap.
src/workflow-store.test.ts Covers basic activity persistence and usage propagation to agent-call records.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  P[Local agent provider] --> O[LocalAgentObservation]
  O --> A[WorkflowStore.appendAgentObservation]
  A --> T[(workflow_agent_observations)]
  A -->|usage observation| C[(workflow_agent_calls.usage_json)]
  F[completeAgentCall] -->|final usage| C
Loading

Reviews (1): Last reviewed commit: "feat(workflow): add observability storag..." | Re-trigger Greptile

Comment thread src/workflow-store.ts
Comment on lines +903 to +905
: null;
const dataJson = input.dataJson
? truncateText(input.dataJson, WORKFLOW_LIMITS.observationDataJsonBytes, "dataJson")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Preserve capped payload JSON

Oversized dataJson values are truncated at an arbitrary byte boundary, leaving stored diagnostics with invalid JSON or a replacement character when a multibyte character is split. Use JSON-aware truncation so consumers can reliably parse the capped payload.

@Waishnav

Waishnav commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/workflow-store.ts`:
- Around line 898-945: Update appendAgentObservation to read the existing
usage_json for the matching workflow_agent_calls row within the transaction,
merge it with observation.usage when processing usage observations, and persist
the merged usage while leaving the inserted observation row unchanged. Add a
regression test covering two partial usage observations with different token
fields and verify the agent call contains both fields.
- Around line 1308-1313: Update truncateText to avoid decoding a partial UTF-8
code point: after slicing the first maxBytes bytes, back up over trailing UTF-8
continuation bytes before converting the buffer to a string. Ensure the returned
truncated value never exceeds maxBytes while preserving the existing error
behavior for an empty result.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 671f75bb-8e05-4b70-8a76-f2edeed7c5db

📥 Commits

Reviewing files that changed from the base of the PR and between b0c0ff1 and 518ba2c.

📒 Files selected for processing (7)
  • src/db/migrations.ts
  • src/db/schema.ts
  • src/local-agent-observations.ts
  • src/oauth-store.test.ts
  • src/workflow-store.test.ts
  • src/workflow-store.ts
  • src/workflow-types.ts

Comment thread src/workflow-store.ts
Comment on lines +898 to +945
appendAgentObservation(input: AppendAgentObservationInput): WorkflowAgentObservationRecord {
const call = this.requireAgentCall(input.runId, input.callIndex);
const observation = input.observation;
const usageJson = observation.kind === "usage"
? JSON.stringify(observation.usage)
: null;
const dataJson = input.dataJson
? truncateText(input.dataJson, WORKFLOW_LIMITS.observationDataJsonBytes, "dataJson")
: null;
const createdAt = isoNow();
const transaction = this.database.sqlite.transaction(() => {
const next = this.database.sqlite
.prepare(
`select coalesce(max(seq), 0) + 1 as next_seq
from workflow_agent_observations where run_id = ? and call_index = ?`,
)
.get(input.runId, input.callIndex) as { next_seq: number };
this.database.sqlite
.prepare(
`insert into workflow_agent_observations (
run_id, call_index, seq, provider, kind, activity_id, message,
tool_name, tool_status, detail, usage_json, data_json, created_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
input.runId,
input.callIndex,
next.next_seq,
call.provider,
observation.kind,
observation.kind === "activity" ? observation.activityId ?? null : null,
observation.kind === "activity" ? observation.message ?? null : null,
observation.kind === "activity" ? observation.toolName ?? null : null,
observation.kind === "activity" ? observation.toolStatus ?? null : null,
observation.kind === "activity" ? observation.detail ?? null : null,
usageJson,
dataJson,
createdAt,
);
if (usageJson) {
this.database.sqlite
.prepare(
`update workflow_agent_calls
set usage_json = ?, updated_at = ?
where run_id = ? and call_index = ?`,
)
.run(usageJson, createdAt, input.runId, input.callIndex);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Merge partial usage observations before updating the agent call.

Lines 937-944 replace usage_json with the newest payload. This drops fields from earlier partial observations. Read the current usage inside the transaction, merge it with observation.usage, and persist the merged value. Keep the individual observation row unchanged.

Add a regression test with two partial usage observations that contain different token fields.

Based on PR objectives, the store must aggregate usage observations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workflow-store.ts` around lines 898 - 945, Update appendAgentObservation
to read the existing usage_json for the matching workflow_agent_calls row within
the transaction, merge it with observation.usage when processing usage
observations, and persist the merged usage while leaving the inserted
observation row unchanged. Add a regression test covering two partial usage
observations with different token fields and verify the agent call contains both
fields.

Comment thread src/workflow-store.ts
Comment on lines +1308 to +1313
function truncateText(value: string, maxBytes: number, label: string): string {
if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
const truncated = Buffer.from(value, "utf8").subarray(0, maxBytes).toString("utf8");
if (!truncated) throw new Error(`${label} exceeds limit (${maxBytes} bytes)`);
return truncated;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve the byte limit at UTF-8 boundaries.

Line 1310 can decode a partial code point as U+FFFD. The replacement character can make the returned value exceed maxBytes. Back up over UTF-8 continuation bytes before decoding the slice.

Proposed fix
 function truncateText(value: string, maxBytes: number, label: string): string {
   if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
-  const truncated = Buffer.from(value, "utf8").subarray(0, maxBytes).toString("utf8");
+  const bytes = Buffer.from(value, "utf8");
+  let end = Math.min(maxBytes, bytes.length);
+  while (end > 0 && ((bytes[end] ?? 0) & 0b1100_0000) === 0b1000_0000) end -= 1;
+  const truncated = bytes.subarray(0, end).toString("utf8");
   if (!truncated) throw new Error(`${label} exceeds limit (${maxBytes} bytes)`);
   return truncated;
 }

Based on PR objectives, observation diagnostic data is size-capped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workflow-store.ts` around lines 1308 - 1313, Update truncateText to avoid
decoding a partial UTF-8 code point: after slicing the first maxBytes bytes,
back up over trailing UTF-8 continuation bytes before converting the buffer to a
string. Ensure the returned truncated value never exceeds maxBytes while
preserving the existing error behavior for an empty result.

@Waishnav Waishnav closed this Aug 9, 2026
@Waishnav

Waishnav commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Closing this stack in favor of the alternative observability implementation. Review surfaced unresolved storage-contract issues here, including invalid JSON truncation and retention semantics that did not actually bound persisted observations. The provider-neutral shape is still a useful design reference, but this PR will not be carried forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant