After context compaction, the preserved last assistant message retained its structured tool_calls field, but the corresponding tool_result was summarized away. This created orphaned tool_use blocks that violated the Anthropic API constraint: 'Each tool_use block must have a corresponding tool_result block in the next message', causing 400 errors. Primary fix: clear tool_calls from the preserved assistant message in extract_preserved_messages(). The tool call was already executed and its result is captured in the summary. Defense-in-depth: added strip_orphaned_tool_use() post-processing in Anthropic convert_messages() to detect and strip any orphaned tool_use blocks before they reach the API. Added 7 tests: 3 unit tests for compaction stripping, 3 unit tests for Anthropic orphan detection, 1 integration test reproducing the exact bug scenario from the h3 session.
28 KiB
Workspace Memory
Updated: 2026-02-11T03:39:03Z | Size: 29.0k chars
Remember Tool Wiring
crates/g3-core/src/tools/memory.rs[0..5000] -execute_remember(),get_memory_path(),merge_memory()crates/g3-core/src/tool_definitions.rs[11000..12000] - remember tool increate_core_tools()crates/g3-core/src/tool_dispatch.rs[48] - dispatch casecrates/g3-core/src/prompts.rs[4200..6500] - Workspace Memory prompt sectioncrates/g3-cli/src/project_files.rs-read_workspace_memory()loadsanalysis/memory.md
Context Window & Compaction
crates/g3-core/src/context_window.rs[0..29568]ThinResult[23] - scope, before/after %, chars_savedContextWindow- token tracking, message historyreset_with_summary()- compact history to summaryshould_compact()- threshold check (80%)thin_context()- replace large results with file refs
crates/g3-core/src/compaction.rs[0..11404]CompactionResult,CompactionConfig- result/config structsperform_compaction()- unified for force_compact() and auto-compactioncalculate_capped_summary_tokens(),should_disable_thinking()build_summary_messages(),apply_summary_fallback_sequence()
crates/g3-core/src/lib.rs-Agent.force_compact(),stream_completion_with_tools()
Session Storage & Continuation
crates/g3-core/src/session_continuation.rs[0..541] -SessionContinuation,save_continuation(),load_continuation()crates/g3-core/src/paths.rs[0..133] -get_session_logs_dir(),get_thinned_dir(),get_session_file()crates/g3-core/src/session.rs- Session logging utilities
Tool System
crates/g3-core/src/tool_definitions.rs[0..544] -create_core_tools(),create_tool_definitions(),ToolConfigcrates/g3-core/src/tool_dispatch.rs[0..73] -dispatch_tool()routing
CLI Module Structure
crates/g3-cli/src/lib.rs[0..415] -run(), mode dispatch, config loadingcrates/g3-cli/src/cli_args.rs[0..133] -Clistruct (clap)crates/g3-cli/src/autonomous.rs[0..785] -run_autonomous(), coach-player loopcrates/g3-cli/src/agent_mode.rs[0..284] -run_agent_mode(),Agent::new_with_custom_prompt()crates/g3-cli/src/accumulative.rs[0..343] -run_accumulative_mode()crates/g3-cli/src/interactive.rs[0..851] -run_interactive(),run_interactive_machine(), REPLcrates/g3-cli/src/task_execution.rs[0..212] -execute_task_with_retry(),OutputModecrates/g3-cli/src/commands.rs[17..320] -/help,/compact,/thinnify,/fragments,/rehydratecrates/g3-cli/src/utils.rs[0..91] -display_welcome_message(),get_workspace_path()crates/g3-cli/src/display.rs-format_workspace_path(),LoadedContent,print_loaded_status()
Auto-Memory System
crates/g3-core/src/lib.rssend_auto_memory_reminder()[47800..48800] - MEMORY CHECKPOINT promptset_auto_memory()[1451..1454] - enable/disabletool_calls_this_turn[116] - tracks tools per turnexecute_tool_in_dir()[2843..2855] - records tool calls
crates/g3-core/src/prompts.rs[3800..4500] - Memory Format in system promptcrates/g3-cli/src/lib.rs[393] ---auto-memoryCLI flag
Streaming Markdown Formatter
crates/g3-cli/src/streaming_markdown.rsformat_header()[21500..22500] - headers with inline formattingprocess_in_code_block()[439..462] - detects closing fenceemit_code_block()[654..675] - joins buffer, highlights codeflush_incomplete()[693..735] - handles unclosed blocks at stream end
crates/g3-cli/tests/streaming_markdown_test.rs- header formatting tests- Gotcha: closing ``` without trailing newline must be detected in
flush_incomplete()
Retry Infrastructure
crates/g3-core/src/retry.rs[0..12000] -execute_with_retry(),retry_operation(),RetryConfig,RetryResultcrates/g3-cli/src/task_execution.rs-execute_task_with_retry()
UI Abstraction Layer
crates/g3-core/src/ui_writer.rs[0..4500] -UiWritertrait,NullUiWriter,print_thin_result()crates/g3-cli/src/ui_writer_impl.rs[0..14000] -ConsoleUiWriter,print_tool_compact()crates/g3-cli/src/simple_output.rs[0..1200] -SimpleOutputhelper
Feedback Extraction
crates/g3-core/src/feedback_extraction.rs[0..22000] -extract_coach_feedback(),try_extract_from_session_log(),try_extract_from_native_tool_call()crates/g3-cli/src/coach_feedback.rs[0..4025] -extract_from_logs()for coach-player loop
Streaming Utilities & State
crates/g3-core/src/streaming.rs[0..26146]MAX_ITERATIONS[13] - constant (400)StreamingState[16] - cross-iteration: full_response, first_token_time, iteration_countToolOutputFormat[54] - enum: SelfHandled, Compact(String), RegularIterationState[166] - per-iteration: parser, current_response, tool_executedtruncate_line(),truncate_for_display(),log_stream_error(),is_connection_error()format_tool_result_summary(),is_compact_tool(),format_compact_tool_summary()
crates/g3-core/src/lib.rs[1879..2712] -stream_completion_with_tools()main loop
Background Process Management
crates/g3-core/src/background_process.rs[0..3000] -BackgroundProcessManager,start(),list(),is_running(),get(),remove()- No
stop()method - use shellkill <pid>
Unified Diff Application
crates/g3-core/src/utils.rs[5000..15000] -apply_unified_diff_to_string(),parse_unified_diff_hunks()- Handles multi-hunk diffs, CRLF normalization, range constraints
Error Classification
crates/g3-core/src/error_handling.rs[0..567] -classify_error(),ErrorType,RecoverableError- Priority: rate limit > network > server > busy > timeout > token limit > context length
- Gotcha: "Connection timeout" → NetworkError (not Timeout) due to "connection" keyword priority
CLI Metrics
crates/g3-cli/src/metrics.rs[0..5416] -TurnMetrics,format_elapsed_time(),generate_turn_histogram()
ACD (Aggressive Context Dehydration)
Saves conversation fragments to disk, replaces with stubs.
crates/g3-core/src/acd.rs[0..22830]Fragment-new(),save(),load(),generate_stub(),list_fragments(),get_latest_fragment_id()
crates/g3-core/src/tools/acd.rs[0..8500] -execute_rehydrate()toolcrates/g3-core/src/paths.rs[3200..3400] -get_fragments_dir()→.g3/sessions/<id>/fragments/crates/g3-core/src/compaction.rs[195..240] - ACD integration, creates fragment+stubcrates/g3-core/src/context_window.rs[10100..10700] -reset_with_summary_and_stub()crates/g3-cli/src/lib.rs[157..161] ---acdflag; [1476..1525] -/fragments,/rehydrate
Fragment JSON: fragment_id, created_at, messages, message_count, user_message_count, assistant_message_count, tool_call_summary, estimated_tokens, topics, preceding_fragment_id
UTF-8 Safe String Slicing
Rust &s[..n] panics on multi-byte chars (emoji, CJK) if sliced mid-character.
Pattern: s.char_indices().nth(n).map(|(i,_)| i).unwrap_or(s.len())
Danger zones: Display truncation, ACD stubs, user input, non-ASCII text.
Studio - Multi-Agent Workspace Manager
crates/studio/src/main.rs[0..12500] -cmd_run(),cmd_status(),cmd_accept(),cmd_discard(),extract_session_summary()crates/studio/src/session.rs-Session,SessionStatuscrates/studio/src/git.rs-GitWorktreefor isolated agent sessions
Session log: <worktree>/.g3/sessions/<session_id>/session.json
Fields: context_window.{conversation_history, percentage_used, total_tokens, used_tokens}, session_id, status, timestamp
Racket Code Search Support
crates/g3-core/src/code_search/searcher.rs- Racket parser [~45] -
tree_sitter_racket::LANGUAGE - Extensions [~90] -
.rkt,.rktl,.rktd→ "racket"
- Racket parser [~45] -
Language-Specific Prompt Injection
Auto-detects languages and injects toolchain guidance.
crates/g3-cli/src/language_prompts.rsLANGUAGE_PROMPTS[12..19] - (lang_name, extensions, prompt_content)AGENT_LANGUAGE_PROMPTS[21..26] - (agent_name, lang_name, prompt_content)detect_languages()[22..32] - scans workspacescan_directory_for_extensions()[42..77] - recursive, depth 2, skips hidden/vendorget_language_prompts_for_workspace()[88..108]get_agent_language_prompts_for_workspace()[124..137]
crates/g3-cli/src/agent_mode.rs[149..159] - appends agent-specific promptsprompts/langs/- language prompt files
To add language: Create prompts/langs/<lang>.md, add to LANGUAGE_PROMPTS
To add agent+lang: Create prompts/langs/<agent>.<lang>.md, add to AGENT_LANGUAGE_PROMPTS
MockProvider for Testing
crates/g3-providers/src/mock.rsMockProvider[220..320] - response queue, request trackingMockResponse[35..200] - configurable chunks and usagescenariosmodule [410..480] -text_only_response(),multi_turn(),tool_then_response()
crates/g3-core/tests/mock_provider_integration_test.rs- integration tests
Usage: MockProvider::new().with_response(MockResponse::text("Hello!"))
G3 Status Message Formatting
crates/g3-cli/src/g3_status.rsStatus[12] - enum: Done, Failed, Error(String), Custom(String), Resolved, Insufficient, NoChangesG3Status[44] - static methods for "g3:" prefixed messagesprogress()[48] - "g3: ..." (no newline)done()[72] - bold green "[done]"failed()[81] - red "[failed]"thin_result()[236] - formats ThinResult with colors
Prompt Cache Statistics
crates/g3-providers/src/lib.rs[195..210] -Usage.cache_creation_tokens,cache_read_tokenscrates/g3-providers/src/anthropic.rs[944..956] - parsescache_creation_input_tokens,cache_read_input_tokenscrates/g3-providers/src/openai.rs[494..510] - parsesprompt_tokens_details.cached_tokenscrates/g3-core/src/lib.rs[75..90] -CacheStatsstruct; [106] -Agent.cache_statscrates/g3-core/src/stats.rs[189..230] -format_cache_stats()with hit rate metrics
Embedded Provider (Local LLM)
Local inference via llama-cpp-rs with Metal acceleration.
crates/g3-providers/src/embedded.rsEmbeddedProvider[22..85] - session, model_name, max_tokens, temperature, context_lengthnew()[26..85] - tilde expansion, auto-downloads Qwen if missingformat_messages()[87..175] - converts to prompt string (Qwen/Mistral/Llama templates)get_stop_sequences()[280..340] - model-specific stop tokensstream()[560..780] - via spawn_blocking + mpsc
Chat Template Formats
| Model | Start Token | End Token |
|---|---|---|
| Qwen | <|im_start|>role\n |
<|im_end|> |
| GLM-4 | [gMASK]<sop><|role|>\n |
<|endoftext|> |
| Mistral | <s>[INST] |
[/INST] |
| Llama | <<SYS>> |
<</SYS>> |
Recommended GGUF Models
| Model | Size | Use Case |
|---|---|---|
| GLM-4-9B-Q8_0 | ~10GB | Fast, capable |
| GLM-4-32B-Q6_K_L | ~27GB | Top tier coding/reasoning |
| Qwen3-4B-Q4_K_M | ~2.3GB | Small, rivals 72B |
Download: huggingface-cli download <repo> --include "<file>" --local-dir ~/.g3/models/
Config:
[providers.embedded.glm4]
model_path = "~/.g3/models/THUDM_GLM-4-32B-0414-Q6_K_L.gguf"
model_type = "glm4"
context_length = 32768
max_tokens = 4096
gpu_layers = 99
Agent Skills System
Portable skill packages with SKILL.md + optional scripts per Agent Skills spec (agentskills.io).
crates/g3-core/src/skills/mod.rs[0..47] - exports:Skill,discover_skills,generate_skills_promptcrates/g3-core/src/skills/parser.rs[0..363]Skill[11..30] - name, description, metadata, body, pathSkill::parse()[45..100] - parses SKILL.md with YAML frontmattervalidate_name()[133..175] - 1-64 chars, lowercase+hyphens
crates/g3-core/src/skills/discovery.rs[0..383]discover_skills()[38..85] - scans 5 locations: embedded → global → extra → workspace → repoload_embedded_skills()[88..102] - synthetic path<embedded:name>/SKILL.mdis_embedded_skill()[161..163] - checks<embedded:prefix
crates/g3-core/src/skills/embedded.rs[0..55]EmbeddedSkill[15..20] - name, skill_mdEMBEDDED_SKILLS[27] - static array (currently empty)
crates/g3-core/src/skills/extraction.rs[0..234]extract_script()[28..85] - extracts to.g3/bin/, tracks version hashneeds_update()[107..118] - compares stored hash vs content
crates/g3-core/src/skills/prompt.rs[0..140]generate_skills_prompt()[12..40] - generates<available_skills>XML
crates/g3-config/src/lib.rs[180..200] -SkillsConfig(enabled, extra_paths)crates/g3-cli/src/project_files.rs[180..210] -discover_and_format_skills()
Skill Locations (priority: later overrides earlier):
- Embedded (compiled in)
~/.g3/skills/(global)- Config extra_paths
.g3/skills/(workspace)skills/(repo root)
SKILL.md Format:
---
name: skill-name # Required: 1-64 chars, lowercase + hyphens
description: What it does # Required: 1-1024 chars
license: Apache-2.0 # Optional
compatibility: Requires X # Optional
---
# Instructions...
Research Tool (First-Class)
Async web research via background scout agent. Implemented as a first-class tool (not a skill).
crates/g3-core/src/pending_research.rs[0..547]PendingResearchManager- thread-safe task tracking with ArcResearchTask,ResearchStatus- task state (Pending/Complete/Failed)register(),complete(),fail(),get(),list_pending(),take_completed()with_notifications()- broadcast channel for interactive mode
crates/g3-core/src/tools/research.rs[0..471]execute_research()- spawns scout agent in background tokio taskexecute_research_status()- check status of pending/completed researchCONTEXT_ERROR_PATTERNS- detects context window exhaustionstrip_ansi_codes(),extract_report()- report extraction utilities
crates/g3-core/src/lib.rsAgent.pending_research_manager- field on Agent structinject_completed_research()[781..836] - injects results as user messagesenable_research_notifications()- for interactive mode
Tools: research (async, returns research_id), research_status (check pending tasks)
Plan Mode
Structured task planning with cognitive forcing - requires happy/negative/boundary checks.
crates/g3-core/src/tools/plan.rsPlan[200..240] - plan_id, revision, approved_revision, items[]PlanItem[110..145] - id, description, state, touches, checks, evidence, notesPlanState[25..45] - enum: Todo, Doing, Done, BlockedChecks[90..105] - happy, negative[], boundary[]get_plan_path()[280..285] -.g3/sessions/<id>/plan.g3.mdread_plan(),write_plan()[290..335] - YAML in markdownplan_verify()[659..700] - verifies evidence when completeexecute_plan_read/write/approve()[395..530] - tool implementations
crates/g3-core/src/tool_definitions.rs[263..330] - plan_read, plan_write, plan_approvecrates/g3-core/src/prompts.rs[21..130] - SHARED_PLAN_SECTION
Tool names: plan_read, plan_write, plan_approve (underscores, not dots)
Plan Verification System
crates/g3-core/src/tools/plan.rsEvidenceType[283..300] - CodeLocation, TestReference, UnknownVerificationStatus[303..320] - Verified, Warning, Error, Skippedparse_evidence()[390..428] - parsesfile:line-lineorfile::test_nameverify_code_location()[443..495] - checks file exists, lines in rangeverify_test_reference()[496..554] - checks test file, searches for fn
Evidence formats: src/foo.rs:42-118, src/foo.rs:42, tests/foo.rs::test_bar
Invariants System (Rulespec & Envelope)
Machine-readable invariants for Plan Mode verification.
crates/g3-core/src/tools/invariants.rsClaim[50..75] - name + selectorPredicateRule[80..120] - Contains, Equals, Exists, NotExists, GreaterThan, LessThan, MinLength, MaxLength, MatchesPredicate[125..180] - claim, rule, value, source, notesRulespec[185..240] - claims[] + predicates[]ActionEnvelope[245..290] - facts HashMapSelector[295..410] - XPath-like:foo.bar,foo[0],foo[*]evaluate_rulespec()[780..850] - evaluates against envelope- Paths:
.g3/sessions/<id>/rulespec.yaml,envelope.yaml
Studio SDLC Pipeline
Orchestrates 7 agents in sequence for codebase maintenance.
crates/studio/src/sdlc.rsPIPELINE_STAGES[28..62] - euler → breaker → hopper → fowler → carmack → lamport → huffmanStage[18..26] - name, description, focusStageStatus[65..80] - Pending, Running, Complete, Failed, SkippedPipelineState[108..140] - run_id, stages[], commit_cursor, session_iddisplay_pipeline()[354..390] - box display with status icons
crates/studio/src/main.rscmd_sdlc_run()[540..655] - orchestrates pipeline, merges on completionhas_commits_on_branch()[715..728] - counts commits ahead of main
crates/studio/src/git.rs-merge_to_main()(hardcodes 'main')
State: .g3/sdlc/pipeline.json
CLI: studio sdlc run [-c N], studio sdlc status, studio sdlc reset
Terminal Width Responsive Output
Makes tool output responsive to terminal width - no line wrapping, with 4-char right margin.
crates/g3-cli/src/terminal_width.rsget_terminal_width()[21..28] - returns usable width (terminal - 4 margin), min 40, default 80clip_line()[33..44] - clips line with "…" ellipsis, UTF-8 safecompress_path()[53..96] - preserves filename, truncates dirs from left with "…"compress_command()[101..103] - clips command from right with "…"available_width_after_prefix()[115..117] - helper for prefixed lines
crates/g3-cli/src/ui_writer_impl.rsupdate_tool_output_line()[407..445] - uses clip_line() with dynamic widthprint_tool_output_line()[447..454] - uses clip_line() for output linesprint_tool_output_header()[293..410] - uses compress_path/compress_commandprint_tool_compact()[475..635] - width-aware compact tool display
Datalog Invariant Verification
crates/g3-core/src/tools/datalog.rs[0..37000]CompiledPredicate[47..67] - id, claim_name, selector, rule, expected_value, source, notesCompiledRulespec[70..80] - plan_id, compiled_at_revision, predicates, claimscompile_rulespec()[88..140] - validates selectors, builds claim lookup, converts to CompiledPredicateFact[170..180] - claim_name, value (extracted from envelope)extract_facts()[190..210] - uses Selector to navigate envelope YAMLextract_values_recursive()[215..250] - handles arrays/objects/scalars, adds __length factsDatalogPredicateResult[255..275] - id, claim_name, rule, expected_value, passed, reason, source, notesDatalogExecutionResult[280..295] - predicate_results, fact_count, passed_count, failed_countexecute_rules()[300..340] - builds fact lookup, uses datafrog Iteration, evaluates predicatesevaluate_predicate_datalog()[345..480] - handles all PredicateRule typesget_compiled_rulespec_path()[500..505] -.g3/sessions/<id>/rulespec.compiled.jsonsave_compiled_rulespec(),load_compiled_rulespec()[510..530] - JSON serializationformat_datalog_results()[540..620] - formats results for shadow mode display
crates/g3-core/src/tools/plan.rsshadow_datalog_verify()[716..760] - loads compiled rulespec + envelope, runs datalog, prints to stderrexecute_plan_approve()[1030..1095] - compiles rulespec on approval, saves to rulespec.compiled.json
Datalog Flow:
plan_approve→compile_rulespec()→ savesrulespec.compiled.jsonplan_verify→shadow_datalog_verify()→ loads compiled + envelope →extract_facts()→execute_rules()→eprint!()(shadow mode)
Rulespec Changes (2026-02-06)
- Rulespec is no longer generated on-the-fly during
plan_write— it's now read fromanalysis/rulespec.yaml(checked-in, hand-crafted) read_rulespec()ininvariants.rsnow takes&Path(working_dir) instead of&str(session_id)write_rulespec(),get_rulespec_path(),format_rulespec_yaml(),format_rulespec_markdown()removed frominvariants.rssave_compiled_rulespec(),load_compiled_rulespec(),get_compiled_rulespec_path()removed fromdatalog.rsshadow_datalog_verify()now compiles rulespec on-the-fly at verify time, writesrulespec.compiled.dlanddatalog_evaluation.txtto session dirplan_writetool no longer acceptsrulespecparameterplan_approveno longer compiles rulespecformat_verification_results()now takesworking_dir: Option<&Path>as third parameter
Write Envelope Tool
crates/g3-core/src/tools/envelope.rs[0..184]execute_write_envelope()[37..79] - parses YAML facts, writes envelope.yaml, calls verify_envelope()verify_envelope()[93..183] - compiles rulespec, extracts facts, runs datalog, writes .dl + evaluation artifacts (shadow mode)
crates/g3-core/src/tools/mod.rs[16] -pub mod envelope;crates/g3-core/src/tool_definitions.rs[266..282] - write_envelope tool definition (facts parameter)crates/g3-core/src/tool_dispatch.rs[41..43] - write_envelope dispatch caseprompts/system/native.md[78..100] - Action Envelope section references write_envelope tool- Tool count: 14 (was 13)
Workflow change: write_envelope → verify_envelope() → datalog shadow, then plan_write(done) → plan_verify() → checks envelope exists
shadow_datalog_verify()removed fromplan.rsformat_verification_results()no longer runs datalog, only checks envelope existence
Datalog Program Generation
crates/g3-core/src/tools/datalog.rs[537..701] -format_datalog_program(),escape_datalog_string()- Soufflé-style .dl output with
.declrelations, fact assertions, and rules - Relations:
claim_value(claim, value),claim_length(claim, length),predicate_pass(id),predicate_fail(id) - Handles all 9 PredicateRule types: Exists, NotExists, Equals, Contains, GreaterThan, LessThan, MinLength, MaxLength, Matches
- Length facts (
__lengthsuffix) go intoclaim_lengthrelation
- Soufflé-style .dl output with
crates/g3-core/src/tools/envelope.rs[150] -verify_envelope()now callsformat_datalog_program()instead ofserde_yaml::to_string()- Bug fixed:
.dlfiles previously contained YAML (just serialized CompiledRulespec), now contain actual Soufflé datalog
Datalog Program Generation
crates/g3-core/src/tools/datalog.rs[537..701] -format_datalog_program(),escape_datalog_string()- Soufflé-style .dl output with
.declrelations, fact assertions, and rules - Relations:
claim_value(claim, value),claim_length(claim, length),predicate_pass(id),predicate_fail(id) - Handles all 9 PredicateRule types: Exists, NotExists, Equals, Contains, GreaterThan, LessThan, MinLength, MaxLength, Matches
- Length facts (
__lengthsuffix) go intoclaim_lengthrelation
- Soufflé-style .dl output with
crates/g3-core/src/tools/envelope.rs[150] -verify_envelope()now callsformat_datalog_program()instead ofserde_yaml::to_string()- Bug fixed:
.dlfiles previously contained YAML (just serialized CompiledRulespec), now contain actual Soufflé datalog
Datalog Fact Extraction Fix (2026-02-07)
crates/g3-core/src/tools/datalog.rs[188..207] -extract_facts()now has fallback: if selector returns empty on unwrapped envelope value, retries against afacts-wrapped version. This handles rulespec selectors written asfacts.feature.donewhento_yaml_value()strips thefacts:wrapper.- Root cause:
ActionEnvelope.to_yaml_value()creates a Mapping from thefactsHashMap WITHOUT afactskey wrapper, but rulespec selectors may include afacts.prefix. - New unit tests:
test_extract_facts_with_facts_prefix_selector,test_extract_facts_roundtrip_from_yaml,test_execute_rules_full_pipeline_with_facts_prefix,test_execute_rules_full_pipeline_without_facts_prefix - New integration tests:
test_plan_verify_rulespec_with_facts_prefix_selectors,test_plan_verify_mixed_pass_fail - Strengthened:
test_plan_verify_with_analysis_rulespecnow assertsFacts extracted: 0is NOT in output
Solon Agent (Rulespec Authoring)
agents/solon.md[0..10800] - Interactive rulespec authoring agent prompt- Full reference for all 9 PredicateRule types: exists, not_exists, equals, contains, greater_than, less_than, min_length, max_length, matches
- Selector syntax (dot/index/wildcard), envelope format, verification pipeline
- Mandatory write_envelope validation step, common mistakes section
crates/g3-cli/src/embedded_agents.rs[26] - solon registered in EMBEDDED_AGENTScrates/g3-cli/src/agent_mode.rs[42] - solon in available agents error message- Usage:
g3 --agent solonfor interactive rulespec authoring - Agent count: 9 embedded agents (was 8)
When Condition Bugfix (2026-02-07)
crates/g3-core/src/tools/datalog.rs[377..395] -execute_rules()when condition evaluation- Bug: The
_ =>catch-all in when condition evaluation did naive stringcontainscheck. ForMatches(regex like^Re:), it checked if fact values literally contained the regex pattern string — which never matched. Result: when conditions withmatchesrule always evaluated as not-met → vacuous pass → violations slipped through. - Fix: Replaced hand-rolled when evaluation with synthetic
CompiledPredicatedelegation toevaluate_predicate_datalog(), which handles all 12 rule types correctly. - Tests:
test_execute_rules_when_matches_condition_met,test_execute_rules_when_matches_condition_met_but_predicate_fails,test_execute_rules_when_matches_condition_not_met - Note: The
invariants.rspath was NOT affected — it already delegated toevaluate_predicate()which handles all rules.
Structured Tool Call Messages (2026-02-11)
crates/g3-providers/src/lib.rs[102..106] -MessageToolCallstruct (id, name, input)crates/g3-providers/src/lib.rs[124..131] -Message.tool_calls: Vec<MessageToolCall>,Message.tool_result_id: Option<String>crates/g3-providers/src/anthropic.rs[284..340] -convert_messages()emitstool_useblocks for assistant messages withtool_calls,tool_resultblocks for user messages withtool_result_idcrates/g3-providers/src/anthropic.rs[935..941] -AnthropicContent::ToolResultvariant addedcrates/g3-core/src/lib.rs[82..88] -ToolCall.idfield added (from native providers)crates/g3-core/src/lib.rs[2530..2545] - Tool messages store structuredtool_callsinstead of inline JSON textcrates/g3-core/src/lib.rs[1385..1400] -check_duplicate_in_previous_message()checks structuredtool_callsfieldcrates/g3-core/src/context_window.rs[107..109] -add_message_with_tokens()preserves messages withtool_callseven if content is emptycrates/g3-core/src/streaming_parser.rs[339] -process_chunk()preserves tool callidfrom provider
Bug fixed: Agent would stop mid-task because native tool calls were stored as inline JSON text in Message.content. When sent back to Anthropic API via convert_messages(), they went as plain text instead of structured tool_use/tool_result blocks. The model would occasionally get confused and emit text describing what it wanted to do instead of invoking the tool mechanism.
Compaction Tool Call Stripping Fix (2026-02-11)
crates/g3-core/src/context_window.rs[339..355] -extract_preserved_messages()now stripstool_callsfrom preserved last assistant message- Root cause: After compaction, preserved assistant message retained structured
tool_callsbut the correspondingtool_resultwas summarized away → orphanedtool_useblocks → Anthropic 400 error - Fix: Clear
msg.tool_callsinextract_preserved_messages()before returning - Messages with only tool_calls and empty content are dropped by
add_message_with_tokens()empty check
- Root cause: After compaction, preserved assistant message retained structured
crates/g3-providers/src/anthropic.rs[369..435] -strip_orphaned_tool_use()defense-in-depth- Post-processing pass in
convert_messages()detects orphanedtool_useblocks (no matchingtool_resultin next message) - Strips orphaned blocks with warning, adds placeholder text if message becomes empty
- Post-processing pass in
- Tests:
test_compaction_strips_tool_calls_from_last_assistant,test_compaction_drops_assistant_with_only_tool_calls_no_text,test_compaction_preserves_normal_assistant_message(unit),test_strip_orphaned_tool_use_*(anthropic),test_compaction_strips_structured_tool_calls(integration)