Organize with Workspaces
A workspace is the unit of organization for a migration: it owns its own input/, output/, .sessions/, and logs/ directories. Create one workspace per logical migration (a schema, a domain, a client engagement) rather than dumping unrelated SQL into a single catch-all workspace. This keeps session history, resumability, and audit reports scoped to work that actually belongs together.
One Workspace per Migration
Related views, procedures, and functions that reference each other belong in the same workspace so dependency resolution and session history stay meaningful.
Prune Stale Entries
Run `workspace prune` periodically to clean up registry entries pointing at directories that no longer exist, keeping `workspace list` and `workspace switch` accurate.
# Initialize a workspace for this migration 3xcode workspace init ./billing-migration --name "billing-migration" # Check which workspace is currently active 3xcode workspace status # List all registered workspaces (active one marked with ●) 3xcode workspace list # Switch between workspaces for different migrations 3xcode workspace switch ./billing-migration
Dependency-Aware Bulk Conversion
When SQL objects reference each other (a stored procedure that selects from a view, a function called by three other procedures), don't convert files one at a time in isolation. Use bulk conversion over the whole directory so the pipeline's planning phase can build the full cross-object dependency graph and topologically sort the work before any code is generated.
The planning phase orders conversion by precedence: views and standalone functions first (no dependencies), then any remaining functions, then procedures, then triggers. This way, by the time a dependent object is converted, the real PySpark signature of what it calls already exists rather than being guessed. This is the same reason conversion itself runs in dependency tiers (utilities → standalone → main pipeline): later tiers see the real signatures produced by earlier ones. Converting interrelated objects file-by-file instead of as a batch throws this ordering away.
--schedule Controls Ordering
`simple-first` (default) surfaces quick wins early; `complex-first` front-loads the highest-complexity objects if you'd rather find problems immediately.
Name Your Session
Pass `--session` with a descriptive name so a bulk run is easy to find later in `session list` and easy to resume if interrupted.
# Convert every .sql file in a directory as one dependency-ordered batch 3xcode pyspark convert --dir ./sql/ --session "billing-migration" -w 4 # Prefer to see complex objects converted first 3xcode pyspark convert --dir ./sql/ --schedule complex-first --session "billing-migration" # Resume if a bulk run was interrupted: first find the actual session ID # (auto-generated, not the bare --session name you passed above) 3xcode session list # Then resume using the ID shown in the listing 3xcode pyspark convert --resume <session-id-from-list>
Start with --dry-run
Before committing to a full conversion pass (especially on a large or unfamiliar batch of SQL), run --dry-run first. It executes only the discovery and planning phases: dialect detection, platform fingerprinting, the full object inventory, complexity scoring, and the dependency-ordered conversion plan, with no PySpark code generated. pyspark analyze does the same thing for a single file.
This gives you a cheap first look at what the pipeline thinks it's dealing with: detected dialect and source platform, object count and complexity levels, known limitations, and the planned conversion order, before spending conversion-phase budget on objects that may have been misclassified or that you'd rather exclude.
# Dry-run a bulk batch: discovery + planning only, no conversion 3xcode pyspark convert --dir ./sql/ --dry-run # Analyze a single file the same way 3xcode pyspark analyze legacy_proc.sql
Note that discovery and planning still require login and a valid license: analysis is not a free or offline operation, since it makes real LLM calls. The savings come from skipping the conversion, validation, and auto-fix phases, not from skipping the model entirely.
Review Validation & Audit Reports Before Trusting Output
Every conversion produces a <stem>_audit.md (human-readable) and <stem>_audit.json (canonical) report alongside the generated .py files. Treat these as the first thing you read, not the code. Each output file gets a per-file completion and confidence score, and the effective letter grade (A+ through F) is the lower of the two, so a file can't look better than its weakest signal.
Developer action item buckets
| Bucket | What it means |
|---|---|
auto_fixed | The auto-fix phase found and safely corrected the issue (still worth a spot check). |
requires_manual | Cursors, dynamic SQL, MERGE/upsert logic: patterns with no safe automatic transform. Comes with prescriptive `how_to_fix` guidance. |
infrastructure_setup | Needs external provisioning, not code changes: Delta Lake tables, Change Data Feed, JDBC connections, streaming checkpoints. |
recommended_review | Lower-severity findings surfaced for awareness, never auto-fixed. |
Auto-fix only ever writes a corrected file after two guardrails pass: the fixed content isn't more than 50% shorter than the original, and it parses as valid Python. If either check fails, the fix is reverted and the original file is left untouched, with the reason recorded in the audit. That's a safety net against a bad rewrite, not a guarantee the accepted fix is semantically correct: read the requires_manual and recommended_review sections of the audit for every file before treating a migration as done.
Keep Worker Counts Conservative
The -w / --workers flag controls how many files convert in parallel during a bulk run. 0 (the default) auto-detects a recommended count from available CPU and memory, the same detection surfaced by 3xcode health. Each worker is a full concurrent AI-driven conversion session, and pushing this too high doesn't just consume local resources, it multiplies concurrent LLM calls and API cost bursts across the whole batch at once.
Check the Recommendation First
`3xcode health` reports CPU count, available/total RAM, and a recommended worker count for your machine before you set -w explicitly.
Detached Runs Are Capped Too
Background workers started with `--detach` are capped machine-wide at 3 concurrent sessions, a hard ceiling regardless of workspace.
# System Resources check shows a recommended worker count 3xcode health # Let the CLI auto-detect (default, -w 0) 3xcode pyspark convert --dir ./sql/ # Or set an explicit, conservative count 3xcode pyspark convert --dir ./sql/ -w 4
Use BYOK for Cost Control on Large Migrations
Every plan tier has a monthly conversion cap: Free: 5, Pro: 200, Team: 1,000, Enterprise: unlimited. For a large migration that will blow past a per-tier monthly limit, or where you simply want conversion cost billed directly to your own provider account, Bring Your Own Key (BYOK) routes conversion calls straight to Anthropic, AWS Bedrock, Google Vertex AI, or Azure AI Foundry using your own credentials instead of the platform-provided key.
Keys are stored only locally (OS keychain, with a permissions-locked file fallback) and are never sent to the 3XDE backend; only the provider name and your preferred models are registered server-side. BYOK access is admin-gated, so it needs to be enabled on your account before keys store will complete the flow.
# Request BYOK access if it isn't enabled on your account yet 3xcode contact-admin -m "Please enable BYOK for my account" # Store credentials for your provider of choice 3xcode keys store --provider anthropic # (also supports: bedrock, vertex, azure) # Verify the key actually works before a large run 3xcode keys test --provider anthropic # Confirm which key source is currently active 3xcode keys status
Verify Spark SQL Passthrough Decisions
When discovery classifies a file's dialect as spark_sql (genuine Databricks SQL or Hive), it's already running on the same engine as the PySpark target. There's no correctness gap to close: the conversion decision is about idiom and readability, not semantics, and the pipeline may reasonably choose to keep a block as a spark.sql("...") passthrough rather than rewriting it into the DataFrame API.
Don't accept either direction blindly
A passthrough decision trades DataFrame-API readability and type-safety for a smaller diff from the source. A DataFrame rewrite trades a larger diff for more idiomatic, testable PySpark. Neither is universally correct: review which one the agent chose for each spark_sql object and confirm it matches your team's actual style and maintainability preferences, rather than assuming the rewrite is automatically the better outcome.
Because there's no dialect-translation risk on these files, validation and auto-fix still run and are still worth reading, but treat the audit's findings here as style/maintainability feedback on a same-engine rewrite, not as a correctness gate the way it is for a tsql or plsql conversion.
Quick Reference Checklist
Before You Start
Dedicated workspace initialized. BYOK configured if this migration will exceed your tier's monthly conversion cap.
Before You Convert
Ran --dry-run or pyspark analyze on the batch. Checked `3xcode health` for a recommended worker count.
For Interrelated Objects
Used --dir bulk conversion (not one-file-at-a-time) so dependency ordering applies across views, functions, procedures, and triggers.
Before You Ship
Read every _audit.md, resolved requires_manual items, and reviewed spark_sql passthrough vs. rewrite choices.
| Practice | Command |
|---|---|
| Scope a migration to its own workspace | 3xcode workspace init ./path --name "migration-name" |
| Preview before spending on full conversion | 3xcode pyspark convert --dir ./sql/ --dry-run |
| Check a sane worker count for this machine | 3xcode health |
| Convert interrelated objects as one batch | 3xcode pyspark convert --dir ./sql/ -w 4 --session name |
| Route large migrations to your own provider key | 3xcode keys store --provider anthropic |
| Inspect what needs manual attention | Read `<stem>_audit.md` before merging output |